@looopy-ai/core 1.0.1
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/LICENSE +9 -0
- package/dist/core/agent.d.ts +53 -0
- package/dist/core/agent.js +416 -0
- package/dist/core/cleanup.d.ts +12 -0
- package/dist/core/cleanup.js +45 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/core/iteration.d.ts +5 -0
- package/dist/core/iteration.js +60 -0
- package/dist/core/logger.d.ts +11 -0
- package/dist/core/logger.js +31 -0
- package/dist/core/loop.d.ts +5 -0
- package/dist/core/loop.js +125 -0
- package/dist/core/tools.d.ts +4 -0
- package/dist/core/tools.js +78 -0
- package/dist/core/types.d.ts +30 -0
- package/dist/core/types.js +1 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.js +2 -0
- package/dist/events/utils.d.ts +250 -0
- package/dist/events/utils.js +263 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.js +1 -0
- package/dist/observability/spans/agent-turn.d.ts +31 -0
- package/dist/observability/spans/agent-turn.js +94 -0
- package/dist/observability/spans/index.d.ts +5 -0
- package/dist/observability/spans/index.js +5 -0
- package/dist/observability/spans/iteration.d.ts +14 -0
- package/dist/observability/spans/iteration.js +41 -0
- package/dist/observability/spans/llm-call.d.ts +14 -0
- package/dist/observability/spans/llm-call.js +50 -0
- package/dist/observability/spans/loop.d.ts +14 -0
- package/dist/observability/spans/loop.js +40 -0
- package/dist/observability/spans/tool.d.ts +14 -0
- package/dist/observability/spans/tool.js +44 -0
- package/dist/observability/tracing.d.ts +58 -0
- package/dist/observability/tracing.js +203 -0
- package/dist/providers/chat-completions/aggregate.d.ts +4 -0
- package/dist/providers/chat-completions/aggregate.js +152 -0
- package/dist/providers/chat-completions/content.d.ts +25 -0
- package/dist/providers/chat-completions/content.js +229 -0
- package/dist/providers/chat-completions/index.d.ts +4 -0
- package/dist/providers/chat-completions/index.js +4 -0
- package/dist/providers/chat-completions/streaming.d.ts +12 -0
- package/dist/providers/chat-completions/streaming.js +3 -0
- package/dist/providers/chat-completions/types.d.ts +39 -0
- package/dist/providers/chat-completions/types.js +1 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/litellm-provider.d.ts +43 -0
- package/dist/providers/litellm-provider.js +377 -0
- package/dist/server/event-buffer.d.ts +37 -0
- package/dist/server/event-buffer.js +116 -0
- package/dist/server/event-router.d.ts +31 -0
- package/dist/server/event-router.js +91 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/server/sse.d.ts +60 -0
- package/dist/server/sse.js +159 -0
- package/dist/stores/artifacts/artifact-scheduler.d.ts +50 -0
- package/dist/stores/artifacts/artifact-scheduler.js +86 -0
- package/dist/stores/artifacts/index.d.ts +3 -0
- package/dist/stores/artifacts/index.js +3 -0
- package/dist/stores/artifacts/internal-event-artifact-store.d.ts +54 -0
- package/dist/stores/artifacts/internal-event-artifact-store.js +126 -0
- package/dist/stores/artifacts/memory-artifact-store.d.ts +52 -0
- package/dist/stores/artifacts/memory-artifact-store.js +268 -0
- package/dist/stores/filesystem/filesystem-agent-store.d.ts +18 -0
- package/dist/stores/filesystem/filesystem-agent-store.js +61 -0
- package/dist/stores/filesystem/filesystem-artifact-store.d.ts +59 -0
- package/dist/stores/filesystem/filesystem-artifact-store.js +325 -0
- package/dist/stores/filesystem/filesystem-context-store.d.ts +37 -0
- package/dist/stores/filesystem/filesystem-context-store.js +245 -0
- package/dist/stores/filesystem/filesystem-message-store.d.ts +28 -0
- package/dist/stores/filesystem/filesystem-message-store.js +149 -0
- package/dist/stores/filesystem/filesystem-task-state-store.d.ts +27 -0
- package/dist/stores/filesystem/filesystem-task-state-store.js +220 -0
- package/dist/stores/filesystem/index.d.ts +10 -0
- package/dist/stores/filesystem/index.js +5 -0
- package/dist/stores/index.d.ts +5 -0
- package/dist/stores/index.js +5 -0
- package/dist/stores/memory/memory-state-store.d.ts +15 -0
- package/dist/stores/memory/memory-state-store.js +55 -0
- package/dist/stores/messages/hybrid-message-store.d.ts +29 -0
- package/dist/stores/messages/hybrid-message-store.js +72 -0
- package/dist/stores/messages/index.d.ts +4 -0
- package/dist/stores/messages/index.js +4 -0
- package/dist/stores/messages/interfaces.d.ts +42 -0
- package/dist/stores/messages/interfaces.js +18 -0
- package/dist/stores/messages/mem0-message-store.d.ts +34 -0
- package/dist/stores/messages/mem0-message-store.js +218 -0
- package/dist/stores/messages/memory-message-store.d.ts +27 -0
- package/dist/stores/messages/memory-message-store.js +183 -0
- package/dist/tools/artifact-tools.d.ts +4 -0
- package/dist/tools/artifact-tools.js +277 -0
- package/dist/tools/client-tool-provider.d.ts +25 -0
- package/dist/tools/client-tool-provider.js +139 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/local-tools.d.ts +13 -0
- package/dist/tools/local-tools.js +70 -0
- package/dist/tools/mcp-client.d.ts +29 -0
- package/dist/tools/mcp-client.js +62 -0
- package/dist/tools/mcp-tool-provider.d.ts +22 -0
- package/dist/tools/mcp-tool-provider.js +86 -0
- package/dist/types/a2a.d.ts +36 -0
- package/dist/types/a2a.js +1 -0
- package/dist/types/agent.d.ts +14 -0
- package/dist/types/agent.js +1 -0
- package/dist/types/artifact.d.ts +126 -0
- package/dist/types/artifact.js +1 -0
- package/dist/types/context.d.ts +13 -0
- package/dist/types/context.js +1 -0
- package/dist/types/event.d.ts +360 -0
- package/dist/types/event.js +30 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +9 -0
- package/dist/types/llm.d.ts +24 -0
- package/dist/types/llm.js +1 -0
- package/dist/types/message.d.ts +9 -0
- package/dist/types/message.js +1 -0
- package/dist/types/state.d.ts +86 -0
- package/dist/types/state.js +1 -0
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/tools.js +53 -0
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.js +23 -0
- package/dist/utils/process-signals.d.ts +3 -0
- package/dist/utils/process-signals.js +67 -0
- package/package.json +54 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isDebugEvent } from '../events';
|
|
2
|
+
export class EventRouter {
|
|
3
|
+
subscribers = new Map();
|
|
4
|
+
subscribe(subscriber) {
|
|
5
|
+
const { contextId } = subscriber.config;
|
|
6
|
+
let contextSubscribers = this.subscribers.get(contextId);
|
|
7
|
+
if (!contextSubscribers) {
|
|
8
|
+
contextSubscribers = new Map();
|
|
9
|
+
this.subscribers.set(contextId, contextSubscribers);
|
|
10
|
+
}
|
|
11
|
+
contextSubscribers.set(subscriber.id, subscriber);
|
|
12
|
+
}
|
|
13
|
+
unsubscribe(subscriberId, contextId) {
|
|
14
|
+
const contextSubscribers = this.subscribers.get(contextId);
|
|
15
|
+
if (!contextSubscribers) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
contextSubscribers.delete(subscriberId);
|
|
19
|
+
if (contextSubscribers.size === 0) {
|
|
20
|
+
this.subscribers.delete(contextId);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
route(contextId, event, eventId) {
|
|
24
|
+
const contextSubscribers = this.subscribers.get(contextId);
|
|
25
|
+
if (!contextSubscribers || contextSubscribers.size === 0) {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
let sentCount = 0;
|
|
29
|
+
for (const subscriber of contextSubscribers.values()) {
|
|
30
|
+
if (!event.kind || this.shouldSendToSubscriber(subscriber, event)) {
|
|
31
|
+
try {
|
|
32
|
+
subscriber.send(event, eventId);
|
|
33
|
+
sentCount++;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(`Failed to send event to subscriber ${subscriber.id}:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return sentCount;
|
|
41
|
+
}
|
|
42
|
+
shouldSendToSubscriber(subscriber, event) {
|
|
43
|
+
const { config } = subscriber;
|
|
44
|
+
if (config.taskId && event.taskId !== config.taskId) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (config.filterInternal !== false && isDebugEvent(event)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (config.includeKinds && !config.includeKinds.includes(event.kind)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (config.excludeKinds?.includes(event.kind)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (config.filter && !config.filter(event)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
getSubscriberCount(contextId) {
|
|
62
|
+
return this.subscribers.get(contextId)?.size ?? 0;
|
|
63
|
+
}
|
|
64
|
+
getActiveContexts() {
|
|
65
|
+
return Array.from(this.subscribers.keys());
|
|
66
|
+
}
|
|
67
|
+
getStats() {
|
|
68
|
+
const contexts = this.subscribers.size;
|
|
69
|
+
let totalSubscribers = 0;
|
|
70
|
+
for (const contextSubscribers of this.subscribers.values()) {
|
|
71
|
+
totalSubscribers += contextSubscribers.size;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
totalContexts: contexts,
|
|
75
|
+
totalSubscribers,
|
|
76
|
+
averageSubscribersPerContext: contexts > 0 ? totalSubscribers / contexts : 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
clear() {
|
|
80
|
+
for (const contextSubscribers of this.subscribers.values()) {
|
|
81
|
+
for (const subscriber of contextSubscribers.values()) {
|
|
82
|
+
try {
|
|
83
|
+
subscriber.close();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.subscribers.clear();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { type BufferedEvent, EventBuffer, type EventBufferConfig, } from './event-buffer';
|
|
2
|
+
export { type EventFilter, EventRouter, type Subscriber, type SubscriptionConfig, } from './event-router';
|
|
3
|
+
export { SSEConnection, type SSEConnectionConfig, type SSEResponse, SSEServer, type SSEServerConfig, } from './sse';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AnyEvent } from '../types/event';
|
|
2
|
+
import { EventBuffer, type EventBufferConfig } from './event-buffer';
|
|
3
|
+
import { EventRouter, type Subscriber, type SubscriptionConfig } from './event-router';
|
|
4
|
+
export interface SSEResponse {
|
|
5
|
+
setHeader(name: string, value: string): void;
|
|
6
|
+
write(chunk: string): void;
|
|
7
|
+
end(): void;
|
|
8
|
+
writable?: boolean;
|
|
9
|
+
on?(event: 'close', listener: () => void): void;
|
|
10
|
+
once?(event: 'close', listener: () => void): void;
|
|
11
|
+
removeListener?(event: 'close', listener: () => void): void;
|
|
12
|
+
}
|
|
13
|
+
export interface SSEConnectionConfig {
|
|
14
|
+
subscription: SubscriptionConfig;
|
|
15
|
+
response: SSEResponse;
|
|
16
|
+
lastEventId?: string;
|
|
17
|
+
heartbeatInterval?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class SSEConnection implements Subscriber {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly config: SubscriptionConfig;
|
|
22
|
+
private response;
|
|
23
|
+
private heartbeatTimer?;
|
|
24
|
+
private heartbeatInterval;
|
|
25
|
+
private closed;
|
|
26
|
+
private lastEventId?;
|
|
27
|
+
constructor(id: string, connectionConfig: SSEConnectionConfig);
|
|
28
|
+
send(event: AnyEvent, eventId: string): void;
|
|
29
|
+
private sendHeartbeat;
|
|
30
|
+
private startHeartbeat;
|
|
31
|
+
private stopHeartbeat;
|
|
32
|
+
getLastEventId(): string | undefined;
|
|
33
|
+
close(): void;
|
|
34
|
+
isClosed(): boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface SSEServerConfig {
|
|
37
|
+
eventBuffer?: EventBufferConfig;
|
|
38
|
+
enableBuffering?: boolean;
|
|
39
|
+
enableHeartbeat?: boolean;
|
|
40
|
+
heartbeatInterval?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class SSEServer {
|
|
43
|
+
private router;
|
|
44
|
+
private buffer;
|
|
45
|
+
private connectionCounter;
|
|
46
|
+
private enableBuffering;
|
|
47
|
+
private enableHeartbeat;
|
|
48
|
+
private heartbeatInterval;
|
|
49
|
+
constructor(config?: SSEServerConfig);
|
|
50
|
+
subscribe(response: SSEResponse, config: SubscriptionConfig, lastEventId?: string): SSEConnection;
|
|
51
|
+
emit(contextId: string, event: AnyEvent): number;
|
|
52
|
+
getSubscriberCount(contextId: string): number;
|
|
53
|
+
getActiveContexts(): string[];
|
|
54
|
+
getStats(): {
|
|
55
|
+
router: ReturnType<EventRouter['getStats']>;
|
|
56
|
+
buffer?: ReturnType<EventBuffer['getStats']>;
|
|
57
|
+
};
|
|
58
|
+
clearBuffer(contextId: string): void;
|
|
59
|
+
shutdown(): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { EventBuffer } from './event-buffer';
|
|
2
|
+
import { EventRouter } from './event-router';
|
|
3
|
+
export class SSEConnection {
|
|
4
|
+
id;
|
|
5
|
+
config;
|
|
6
|
+
response;
|
|
7
|
+
heartbeatTimer;
|
|
8
|
+
heartbeatInterval;
|
|
9
|
+
closed = false;
|
|
10
|
+
lastEventId;
|
|
11
|
+
constructor(id, connectionConfig) {
|
|
12
|
+
this.id = id;
|
|
13
|
+
this.config = connectionConfig.subscription;
|
|
14
|
+
this.response = connectionConfig.response;
|
|
15
|
+
this.lastEventId = connectionConfig.lastEventId;
|
|
16
|
+
this.heartbeatInterval = connectionConfig.heartbeatInterval ?? 30000;
|
|
17
|
+
this.response.setHeader('Content-Type', 'text/event-stream');
|
|
18
|
+
this.response.setHeader('Cache-Control', 'no-cache');
|
|
19
|
+
this.response.setHeader('Connection', 'keep-alive');
|
|
20
|
+
this.response.setHeader('X-Accel-Buffering', 'no');
|
|
21
|
+
if (this.response.on) {
|
|
22
|
+
this.response.on('close', () => {
|
|
23
|
+
this.close();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
this.startHeartbeat();
|
|
27
|
+
}
|
|
28
|
+
send(event, eventId) {
|
|
29
|
+
if (this.closed) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const { kind, contextId: _contextId, ...data } = event;
|
|
34
|
+
this.response.write(`id: ${eventId}\n`);
|
|
35
|
+
this.response.write(`event: ${kind}\n`);
|
|
36
|
+
this.response.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
37
|
+
this.lastEventId = eventId;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(`Failed to send event to ${this.id}:`, error);
|
|
41
|
+
this.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
sendHeartbeat() {
|
|
45
|
+
if (this.closed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
if (this.response.writable === false) {
|
|
50
|
+
this.close();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.response.write(': heartbeat\n\n');
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error(`Failed to send heartbeat to ${this.id}:`, error);
|
|
57
|
+
this.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
startHeartbeat() {
|
|
61
|
+
if (this.heartbeatTimer) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.heartbeatTimer = setInterval(() => {
|
|
65
|
+
this.sendHeartbeat();
|
|
66
|
+
}, this.heartbeatInterval);
|
|
67
|
+
this.heartbeatTimer.unref?.();
|
|
68
|
+
}
|
|
69
|
+
stopHeartbeat() {
|
|
70
|
+
if (this.heartbeatTimer) {
|
|
71
|
+
clearInterval(this.heartbeatTimer);
|
|
72
|
+
this.heartbeatTimer = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
getLastEventId() {
|
|
76
|
+
return this.lastEventId;
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
if (this.closed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.closed = true;
|
|
83
|
+
this.stopHeartbeat();
|
|
84
|
+
try {
|
|
85
|
+
this.response.end();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
isClosed() {
|
|
91
|
+
return this.closed;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export class SSEServer {
|
|
95
|
+
router;
|
|
96
|
+
buffer;
|
|
97
|
+
connectionCounter = 0;
|
|
98
|
+
enableBuffering;
|
|
99
|
+
enableHeartbeat;
|
|
100
|
+
heartbeatInterval;
|
|
101
|
+
constructor(config = {}) {
|
|
102
|
+
this.router = new EventRouter();
|
|
103
|
+
this.enableBuffering = config.enableBuffering ?? true;
|
|
104
|
+
this.enableHeartbeat = config.enableHeartbeat ?? true;
|
|
105
|
+
this.heartbeatInterval = config.heartbeatInterval ?? 30000;
|
|
106
|
+
this.buffer = this.enableBuffering ? new EventBuffer(config.eventBuffer) : null;
|
|
107
|
+
}
|
|
108
|
+
subscribe(response, config, lastEventId) {
|
|
109
|
+
const connectionId = `conn-${++this.connectionCounter}`;
|
|
110
|
+
const connection = new SSEConnection(connectionId, {
|
|
111
|
+
subscription: config,
|
|
112
|
+
response,
|
|
113
|
+
lastEventId,
|
|
114
|
+
heartbeatInterval: this.enableHeartbeat ? this.heartbeatInterval : 0,
|
|
115
|
+
});
|
|
116
|
+
this.router.subscribe(connection);
|
|
117
|
+
if (lastEventId && this.buffer) {
|
|
118
|
+
const bufferedEvents = this.buffer.getEventsSince(config.contextId, lastEventId);
|
|
119
|
+
for (const buffered of bufferedEvents) {
|
|
120
|
+
connection.send(buffered.event, buffered.id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const originalClose = connection.close.bind(connection);
|
|
124
|
+
connection.close = () => {
|
|
125
|
+
this.router.unsubscribe(connectionId, config.contextId);
|
|
126
|
+
originalClose();
|
|
127
|
+
};
|
|
128
|
+
return connection;
|
|
129
|
+
}
|
|
130
|
+
emit(contextId, event) {
|
|
131
|
+
let eventId = '';
|
|
132
|
+
if (this.buffer) {
|
|
133
|
+
eventId = this.buffer.add(contextId, event);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
eventId = `${contextId}-${Date.now()}`;
|
|
137
|
+
}
|
|
138
|
+
return this.router.route(contextId, event, eventId);
|
|
139
|
+
}
|
|
140
|
+
getSubscriberCount(contextId) {
|
|
141
|
+
return this.router.getSubscriberCount(contextId);
|
|
142
|
+
}
|
|
143
|
+
getActiveContexts() {
|
|
144
|
+
return this.router.getActiveContexts();
|
|
145
|
+
}
|
|
146
|
+
getStats() {
|
|
147
|
+
return {
|
|
148
|
+
router: this.router.getStats(),
|
|
149
|
+
buffer: this.buffer?.getStats(),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
clearBuffer(contextId) {
|
|
153
|
+
this.buffer?.clear(contextId);
|
|
154
|
+
}
|
|
155
|
+
shutdown() {
|
|
156
|
+
this.router.clear();
|
|
157
|
+
this.buffer?.shutdown();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ArtifactStore, DatasetSchema, StoredArtifact } from '../../types/artifact';
|
|
2
|
+
export declare class ArtifactScheduler implements ArtifactStore {
|
|
3
|
+
private store;
|
|
4
|
+
private queues;
|
|
5
|
+
private processing;
|
|
6
|
+
constructor(store: ArtifactStore);
|
|
7
|
+
createFileArtifact(params: {
|
|
8
|
+
artifactId: string;
|
|
9
|
+
taskId: string;
|
|
10
|
+
contextId: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
encoding?: 'utf-8' | 'base64';
|
|
15
|
+
override?: boolean;
|
|
16
|
+
}): Promise<string>;
|
|
17
|
+
appendFileChunk(contextId: string, artifactId: string, chunk: string, options?: {
|
|
18
|
+
isLastChunk?: boolean;
|
|
19
|
+
encoding?: 'utf-8' | 'base64';
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
getFileContent(contextId: string, artifactId: string): Promise<string>;
|
|
22
|
+
createDataArtifact(params: {
|
|
23
|
+
artifactId: string;
|
|
24
|
+
taskId: string;
|
|
25
|
+
contextId: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
override?: boolean;
|
|
29
|
+
}): Promise<string>;
|
|
30
|
+
writeData(contextId: string, artifactId: string, data: Record<string, unknown>): Promise<void>;
|
|
31
|
+
getDataContent(contextId: string, artifactId: string): Promise<Record<string, unknown>>;
|
|
32
|
+
createDatasetArtifact(params: {
|
|
33
|
+
artifactId: string;
|
|
34
|
+
taskId: string;
|
|
35
|
+
contextId: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
schema?: DatasetSchema;
|
|
39
|
+
override?: boolean;
|
|
40
|
+
}): Promise<string>;
|
|
41
|
+
appendDatasetBatch(contextId: string, artifactId: string, rows: Record<string, unknown>[], options?: {
|
|
42
|
+
isLastBatch?: boolean;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
getDatasetRows(contextId: string, artifactId: string): Promise<Record<string, unknown>[]>;
|
|
45
|
+
getArtifact(contextId: string, artifactId: string): Promise<StoredArtifact | null>;
|
|
46
|
+
listArtifacts(contextId: string, taskId?: string): Promise<string[]>;
|
|
47
|
+
deleteArtifact(contextId: string, artifactId: string): Promise<void>;
|
|
48
|
+
private scheduleOperation;
|
|
49
|
+
private processQueue;
|
|
50
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class ArtifactScheduler {
|
|
2
|
+
store;
|
|
3
|
+
queues = new Map();
|
|
4
|
+
processing = new Map();
|
|
5
|
+
constructor(store) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
}
|
|
8
|
+
async createFileArtifact(params) {
|
|
9
|
+
return this.scheduleOperation(params.artifactId, () => this.store.createFileArtifact(params));
|
|
10
|
+
}
|
|
11
|
+
async appendFileChunk(contextId, artifactId, chunk, options) {
|
|
12
|
+
return this.scheduleOperation(artifactId, () => this.store.appendFileChunk(contextId, artifactId, chunk, options));
|
|
13
|
+
}
|
|
14
|
+
async getFileContent(contextId, artifactId) {
|
|
15
|
+
return this.scheduleOperation(artifactId, () => this.store.getFileContent(contextId, artifactId));
|
|
16
|
+
}
|
|
17
|
+
async createDataArtifact(params) {
|
|
18
|
+
return this.scheduleOperation(params.artifactId, () => this.store.createDataArtifact(params));
|
|
19
|
+
}
|
|
20
|
+
async writeData(contextId, artifactId, data) {
|
|
21
|
+
return this.scheduleOperation(artifactId, () => this.store.writeData(contextId, artifactId, data));
|
|
22
|
+
}
|
|
23
|
+
async getDataContent(contextId, artifactId) {
|
|
24
|
+
return this.scheduleOperation(artifactId, () => this.store.getDataContent(contextId, artifactId));
|
|
25
|
+
}
|
|
26
|
+
async createDatasetArtifact(params) {
|
|
27
|
+
return this.scheduleOperation(params.artifactId, () => this.store.createDatasetArtifact(params));
|
|
28
|
+
}
|
|
29
|
+
async appendDatasetBatch(contextId, artifactId, rows, options) {
|
|
30
|
+
return this.scheduleOperation(artifactId, () => this.store.appendDatasetBatch(contextId, artifactId, rows, options));
|
|
31
|
+
}
|
|
32
|
+
async getDatasetRows(contextId, artifactId) {
|
|
33
|
+
return this.scheduleOperation(artifactId, () => this.store.getDatasetRows(contextId, artifactId));
|
|
34
|
+
}
|
|
35
|
+
async getArtifact(contextId, artifactId) {
|
|
36
|
+
return this.scheduleOperation(artifactId, () => this.store.getArtifact(contextId, artifactId));
|
|
37
|
+
}
|
|
38
|
+
async listArtifacts(contextId, taskId) {
|
|
39
|
+
return this.store.listArtifacts(contextId, taskId);
|
|
40
|
+
}
|
|
41
|
+
async deleteArtifact(contextId, artifactId) {
|
|
42
|
+
return this.scheduleOperation(artifactId, () => this.store.deleteArtifact(contextId, artifactId));
|
|
43
|
+
}
|
|
44
|
+
scheduleOperation(artifactId, operation) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
if (!this.queues.has(artifactId)) {
|
|
47
|
+
this.queues.set(artifactId, []);
|
|
48
|
+
}
|
|
49
|
+
const queue = this.queues.get(artifactId);
|
|
50
|
+
if (!queue) {
|
|
51
|
+
reject(new Error(`Failed to get queue for artifact ${artifactId}`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
queue.push({ operation, resolve, reject });
|
|
55
|
+
if (!this.processing.get(artifactId)) {
|
|
56
|
+
this.processQueue(artifactId);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async processQueue(artifactId) {
|
|
61
|
+
this.processing.set(artifactId, true);
|
|
62
|
+
const queue = this.queues.get(artifactId);
|
|
63
|
+
if (!queue) {
|
|
64
|
+
this.processing.set(artifactId, false);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
const item = queue.shift();
|
|
69
|
+
if (!item) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const result = await item.operation();
|
|
74
|
+
item.resolve(result);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
item.reject(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.processing.set(artifactId, false);
|
|
81
|
+
if (queue.length === 0) {
|
|
82
|
+
this.queues.delete(artifactId);
|
|
83
|
+
this.processing.delete(artifactId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ArtifactStore, DatasetSchema, StoredArtifact } from '../../types/artifact';
|
|
2
|
+
import type { AnyEvent } from '../../types/event';
|
|
3
|
+
export interface InternalEventEmitter {
|
|
4
|
+
emit(event: AnyEvent): void;
|
|
5
|
+
}
|
|
6
|
+
export interface InternalEventArtifactStoreConfig {
|
|
7
|
+
delegate: ArtifactStore;
|
|
8
|
+
eventEmitter: InternalEventEmitter;
|
|
9
|
+
enableEvents?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class InternalEventArtifactStore implements ArtifactStore {
|
|
12
|
+
private delegate;
|
|
13
|
+
private eventEmitter;
|
|
14
|
+
private enableEvents;
|
|
15
|
+
constructor(config: InternalEventArtifactStoreConfig);
|
|
16
|
+
createFileArtifact(params: {
|
|
17
|
+
artifactId: string;
|
|
18
|
+
taskId: string;
|
|
19
|
+
contextId: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
mimeType?: string;
|
|
23
|
+
encoding?: 'utf-8' | 'base64';
|
|
24
|
+
}): Promise<string>;
|
|
25
|
+
appendFileChunk(contextId: string, artifactId: string, chunk: string, options?: {
|
|
26
|
+
isLastChunk?: boolean;
|
|
27
|
+
encoding?: 'utf-8' | 'base64';
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
getFileContent(contextId: string, artifactId: string): Promise<string>;
|
|
30
|
+
createDataArtifact(params: {
|
|
31
|
+
artifactId: string;
|
|
32
|
+
taskId: string;
|
|
33
|
+
contextId: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
}): Promise<string>;
|
|
37
|
+
writeData(contextId: string, artifactId: string, data: Record<string, unknown>): Promise<void>;
|
|
38
|
+
getDataContent(contextId: string, artifactId: string): Promise<Record<string, unknown>>;
|
|
39
|
+
createDatasetArtifact(params: {
|
|
40
|
+
artifactId: string;
|
|
41
|
+
taskId: string;
|
|
42
|
+
contextId: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
schema?: DatasetSchema;
|
|
46
|
+
}): Promise<string>;
|
|
47
|
+
appendDatasetBatch(contextId: string, artifactId: string, rows: Record<string, unknown>[], options?: {
|
|
48
|
+
isLastBatch?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
getDatasetRows(contextId: string, artifactId: string): Promise<Record<string, unknown>[]>;
|
|
51
|
+
getArtifact(contextId: string, artifactId: string): Promise<StoredArtifact | null>;
|
|
52
|
+
listArtifacts(contextId: string, taskId?: string): Promise<string[]>;
|
|
53
|
+
deleteArtifact(contextId: string, artifactId: string): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createDatasetWriteEvent, createDataWriteEvent, createFileWriteEvent, } from '../../events';
|
|
2
|
+
export class InternalEventArtifactStore {
|
|
3
|
+
delegate;
|
|
4
|
+
eventEmitter;
|
|
5
|
+
enableEvents;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.delegate = config.delegate;
|
|
8
|
+
this.eventEmitter = config.eventEmitter;
|
|
9
|
+
this.enableEvents = config.enableEvents ?? true;
|
|
10
|
+
}
|
|
11
|
+
async createFileArtifact(params) {
|
|
12
|
+
return this.delegate.createFileArtifact(params);
|
|
13
|
+
}
|
|
14
|
+
async appendFileChunk(contextId, artifactId, chunk, options) {
|
|
15
|
+
await this.delegate.appendFileChunk(contextId, artifactId, chunk, options);
|
|
16
|
+
if (this.enableEvents) {
|
|
17
|
+
const artifact = await this.delegate.getArtifact(contextId, artifactId);
|
|
18
|
+
if (artifact && artifact.type === 'file') {
|
|
19
|
+
const chunkIndex = artifact.chunks.length - 1;
|
|
20
|
+
const eventOptions = {
|
|
21
|
+
contextId: artifact.contextId,
|
|
22
|
+
taskId: artifact.taskId,
|
|
23
|
+
artifactId,
|
|
24
|
+
data: chunk,
|
|
25
|
+
index: chunkIndex,
|
|
26
|
+
complete: options?.isLastChunk ?? false,
|
|
27
|
+
name: artifact.name,
|
|
28
|
+
description: artifact.description,
|
|
29
|
+
mimeType: artifact.mimeType,
|
|
30
|
+
metadata: {
|
|
31
|
+
encoding: options?.encoding || artifact.encoding,
|
|
32
|
+
chunkSize: Buffer.byteLength(chunk, options?.encoding || artifact.encoding || 'utf-8'),
|
|
33
|
+
totalChunks: artifact.chunks.length,
|
|
34
|
+
totalSize: artifact.totalSize,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
this.eventEmitter.emit(createFileWriteEvent(eventOptions));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async getFileContent(contextId, artifactId) {
|
|
42
|
+
return this.delegate.getFileContent(contextId, artifactId);
|
|
43
|
+
}
|
|
44
|
+
async createDataArtifact(params) {
|
|
45
|
+
return this.delegate.createDataArtifact(params);
|
|
46
|
+
}
|
|
47
|
+
async writeData(contextId, artifactId, data) {
|
|
48
|
+
await this.delegate.writeData(contextId, artifactId, data);
|
|
49
|
+
if (this.enableEvents) {
|
|
50
|
+
const artifact = await this.delegate.getArtifact(contextId, artifactId);
|
|
51
|
+
if (artifact && artifact.type === 'data') {
|
|
52
|
+
const eventOptions = {
|
|
53
|
+
contextId: artifact.contextId,
|
|
54
|
+
taskId: artifact.taskId,
|
|
55
|
+
artifactId,
|
|
56
|
+
data,
|
|
57
|
+
name: artifact.name,
|
|
58
|
+
description: artifact.description,
|
|
59
|
+
metadata: {
|
|
60
|
+
version: artifact.version,
|
|
61
|
+
dataSize: JSON.stringify(data).length,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
this.eventEmitter.emit(createDataWriteEvent(eventOptions));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async getDataContent(contextId, artifactId) {
|
|
69
|
+
return this.delegate.getDataContent(contextId, artifactId);
|
|
70
|
+
}
|
|
71
|
+
async createDatasetArtifact(params) {
|
|
72
|
+
return this.delegate.createDatasetArtifact(params);
|
|
73
|
+
}
|
|
74
|
+
async appendDatasetBatch(contextId, artifactId, rows, options) {
|
|
75
|
+
await this.delegate.appendDatasetBatch(contextId, artifactId, rows, options);
|
|
76
|
+
if (this.enableEvents) {
|
|
77
|
+
const artifact = await this.delegate.getArtifact(contextId, artifactId);
|
|
78
|
+
if (artifact && artifact.type === 'dataset') {
|
|
79
|
+
const batchIndex = artifact.totalChunks - 1;
|
|
80
|
+
const eventOptions = {
|
|
81
|
+
contextId: artifact.contextId,
|
|
82
|
+
taskId: artifact.taskId,
|
|
83
|
+
artifactId,
|
|
84
|
+
rows,
|
|
85
|
+
index: batchIndex,
|
|
86
|
+
complete: options?.isLastBatch ?? false,
|
|
87
|
+
name: artifact.name,
|
|
88
|
+
description: artifact.description,
|
|
89
|
+
schema: artifact.schema
|
|
90
|
+
? {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: Object.fromEntries(artifact.schema.columns.map((col) => [
|
|
93
|
+
col.name,
|
|
94
|
+
{
|
|
95
|
+
type: col.type,
|
|
96
|
+
description: col.description,
|
|
97
|
+
},
|
|
98
|
+
])),
|
|
99
|
+
required: artifact.schema.columns
|
|
100
|
+
.filter((col) => !col.nullable)
|
|
101
|
+
.map((col) => col.name),
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
104
|
+
metadata: {
|
|
105
|
+
batchSize: rows.length,
|
|
106
|
+
totalRows: artifact.totalSize,
|
|
107
|
+
totalBatches: artifact.totalChunks,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
this.eventEmitter.emit(createDatasetWriteEvent(eventOptions));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async getDatasetRows(contextId, artifactId) {
|
|
115
|
+
return this.delegate.getDatasetRows(contextId, artifactId);
|
|
116
|
+
}
|
|
117
|
+
async getArtifact(contextId, artifactId) {
|
|
118
|
+
return this.delegate.getArtifact(contextId, artifactId);
|
|
119
|
+
}
|
|
120
|
+
async listArtifacts(contextId, taskId) {
|
|
121
|
+
return this.delegate.listArtifacts(contextId, taskId);
|
|
122
|
+
}
|
|
123
|
+
async deleteArtifact(contextId, artifactId) {
|
|
124
|
+
return this.delegate.deleteArtifact(contextId, artifactId);
|
|
125
|
+
}
|
|
126
|
+
}
|