@mondaydotcomorg/atp-client 0.17.14
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 +397 -0
- package/dist/client.d.ts +125 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +129 -0
- package/dist/client.js.map +1 -0
- package/dist/core/api-operations.d.ts +37 -0
- package/dist/core/api-operations.d.ts.map +1 -0
- package/dist/core/api-operations.js +90 -0
- package/dist/core/api-operations.js.map +1 -0
- package/dist/core/execution-operations.d.ts +34 -0
- package/dist/core/execution-operations.d.ts.map +1 -0
- package/dist/core/execution-operations.js +237 -0
- package/dist/core/execution-operations.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/provenance-registry.d.ts +40 -0
- package/dist/core/provenance-registry.d.ts.map +1 -0
- package/dist/core/provenance-registry.js +108 -0
- package/dist/core/provenance-registry.js.map +1 -0
- package/dist/core/service-providers.d.ts +29 -0
- package/dist/core/service-providers.d.ts.map +1 -0
- package/dist/core/service-providers.js +139 -0
- package/dist/core/service-providers.js.map +1 -0
- package/dist/core/session.d.ts +50 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +138 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/types.d.ts +73 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/generator.d.ts +7 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +12 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/execute-code.d.ts +20 -0
- package/dist/tools/execute-code.d.ts.map +1 -0
- package/dist/tools/execute-code.js +57 -0
- package/dist/tools/execute-code.js.map +1 -0
- package/dist/tools/explore-api.d.ts +14 -0
- package/dist/tools/explore-api.d.ts.map +1 -0
- package/dist/tools/explore-api.js +47 -0
- package/dist/tools/explore-api.js.map +1 -0
- package/dist/tools/fetch-all-apis.d.ts +14 -0
- package/dist/tools/fetch-all-apis.d.ts.map +1 -0
- package/dist/tools/fetch-all-apis.js +31 -0
- package/dist/tools/fetch-all-apis.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/search-api.d.ts +14 -0
- package/dist/tools/search-api.d.ts.map +1 -0
- package/dist/tools/search-api.js +36 -0
- package/dist/tools/search-api.js.map +1 -0
- package/dist/tools/types.d.ts +23 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +7 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +14 -0
- package/dist/tools.js.map +1 -0
- package/package.json +46 -0
- package/src/client.ts +194 -0
- package/src/core/api-operations.ts +130 -0
- package/src/core/execution-operations.ts +301 -0
- package/src/core/index.ts +13 -0
- package/src/core/provenance-registry.ts +129 -0
- package/src/core/service-providers.ts +176 -0
- package/src/core/session.ts +180 -0
- package/src/core/types.ts +79 -0
- package/src/errors.ts +24 -0
- package/src/generator.ts +15 -0
- package/src/index.ts +10 -0
- package/src/tools/execute-code.ts +76 -0
- package/src/tools/explore-api.ts +63 -0
- package/src/tools/fetch-all-apis.ts +43 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/search-api.ts +48 -0
- package/src/tools/types.ts +24 -0
- package/src/tools.ts +21 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import type { ExecutionResult, ExecutionConfig } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
import { ExecutionStatus } from '@mondaydotcomorg/atp-protocol';
|
|
3
|
+
import type { ClientSession } from './session.js';
|
|
4
|
+
import type { ServiceProviders } from './service-providers';
|
|
5
|
+
import { ClientCallbackError } from '../errors.js';
|
|
6
|
+
import { ProvenanceTokenRegistry } from './provenance-registry.js';
|
|
7
|
+
|
|
8
|
+
export class ExecutionOperations {
|
|
9
|
+
private session: ClientSession;
|
|
10
|
+
private serviceProviders: ServiceProviders;
|
|
11
|
+
private tokenRegistry: ProvenanceTokenRegistry;
|
|
12
|
+
|
|
13
|
+
constructor(session: ClientSession, serviceProviders: ServiceProviders) {
|
|
14
|
+
this.session = session;
|
|
15
|
+
this.serviceProviders = serviceProviders;
|
|
16
|
+
this.tokenRegistry = new ProvenanceTokenRegistry();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Executes code on the server with real-time progress updates via SSE.
|
|
21
|
+
*/
|
|
22
|
+
async executeStream(
|
|
23
|
+
code: string,
|
|
24
|
+
config?: Partial<ExecutionConfig>,
|
|
25
|
+
onProgress?: (message: string, fraction: number) => void
|
|
26
|
+
): Promise<ExecutionResult> {
|
|
27
|
+
await this.session.ensureInitialized();
|
|
28
|
+
|
|
29
|
+
const url = `${this.session.getBaseUrl()}/api/execute/stream`;
|
|
30
|
+
const body = JSON.stringify({ code, config });
|
|
31
|
+
const headers = await this.session.prepareHeaders('POST', url, body);
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const fetchImpl = typeof fetch !== 'undefined' ? fetch : require('undici').fetch;
|
|
35
|
+
|
|
36
|
+
fetchImpl(url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers,
|
|
39
|
+
body,
|
|
40
|
+
})
|
|
41
|
+
.then(async (response: Response) => {
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const reader = response.body?.getReader();
|
|
47
|
+
if (!reader) {
|
|
48
|
+
throw new Error('Response body is not readable');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const decoder = new TextDecoder();
|
|
52
|
+
let buffer = '';
|
|
53
|
+
let result: ExecutionResult | null = null;
|
|
54
|
+
|
|
55
|
+
while (true) {
|
|
56
|
+
const { done, value } = await reader.read();
|
|
57
|
+
|
|
58
|
+
if (done) break;
|
|
59
|
+
|
|
60
|
+
buffer += decoder.decode(value, { stream: true });
|
|
61
|
+
const lines = buffer.split('\n');
|
|
62
|
+
buffer = lines.pop() || '';
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < lines.length; i++) {
|
|
65
|
+
const line = lines[i];
|
|
66
|
+
|
|
67
|
+
if (line && line.startsWith('event:')) {
|
|
68
|
+
const event = line.substring(6).trim();
|
|
69
|
+
|
|
70
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
71
|
+
const dataLine = lines[j];
|
|
72
|
+
if (dataLine && dataLine.startsWith('data:')) {
|
|
73
|
+
const dataStr = dataLine.substring(5).trim();
|
|
74
|
+
if (dataStr) {
|
|
75
|
+
try {
|
|
76
|
+
const data = JSON.parse(dataStr);
|
|
77
|
+
|
|
78
|
+
if (event === 'progress' && onProgress) {
|
|
79
|
+
onProgress(data.message, data.fraction);
|
|
80
|
+
} else if (event === 'result') {
|
|
81
|
+
result = data as ExecutionResult;
|
|
82
|
+
} else if (event === 'error') {
|
|
83
|
+
reject(new Error(data.message));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('Failed to parse SSE data:', dataStr);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (result) {
|
|
98
|
+
resolve(result);
|
|
99
|
+
} else {
|
|
100
|
+
reject(new Error('No result received from server'));
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
.catch(reject);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Executes code on the server in a sandboxed environment.
|
|
109
|
+
*/
|
|
110
|
+
async execute(code: string, config?: Partial<ExecutionConfig>): Promise<ExecutionResult> {
|
|
111
|
+
await this.session.ensureInitialized();
|
|
112
|
+
|
|
113
|
+
const hints = this.tokenRegistry.getRecentTokens(1000);
|
|
114
|
+
|
|
115
|
+
const executionConfig = {
|
|
116
|
+
...config,
|
|
117
|
+
clientServices: {
|
|
118
|
+
hasLLM: !!this.serviceProviders.getLLM(),
|
|
119
|
+
hasApproval: !!this.serviceProviders.getApproval(),
|
|
120
|
+
hasEmbedding: !!this.serviceProviders.getEmbedding(),
|
|
121
|
+
hasTools: this.serviceProviders.hasTools(),
|
|
122
|
+
},
|
|
123
|
+
provenanceHints: hints.length > 0 ? hints : undefined,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const url = `${this.session.getBaseUrl()}/api/execute`;
|
|
127
|
+
const body = JSON.stringify({ code, config: executionConfig });
|
|
128
|
+
const headers = await this.session.prepareHeaders('POST', url, body);
|
|
129
|
+
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers,
|
|
133
|
+
body,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this.session.updateToken(response);
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const error = (await response.json()) as { error: string };
|
|
140
|
+
throw new Error(`Execution failed: ${error.error || response.statusText}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const result = (await response.json()) as ExecutionResult;
|
|
144
|
+
|
|
145
|
+
if (result.provenanceTokens && result.provenanceTokens.length > 0) {
|
|
146
|
+
for (const { token } of result.provenanceTokens) {
|
|
147
|
+
this.tokenRegistry.add(token);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallbacks) {
|
|
152
|
+
return await this.handleBatchCallbacksAndResume(result);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallback) {
|
|
156
|
+
return await this.handlePauseAndResume(result);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Handles batch callbacks by executing them in parallel and resuming.
|
|
164
|
+
*/
|
|
165
|
+
private async handleBatchCallbacksAndResume(
|
|
166
|
+
pausedResult: ExecutionResult
|
|
167
|
+
): Promise<ExecutionResult> {
|
|
168
|
+
if (!pausedResult.needsCallbacks || pausedResult.needsCallbacks.length === 0) {
|
|
169
|
+
throw new Error('No batch callback requests in paused execution');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const batchResults = await Promise.all(
|
|
173
|
+
pausedResult.needsCallbacks.map(async (cb) => {
|
|
174
|
+
const callbackResult = await this.serviceProviders.handleCallback(cb.type, {
|
|
175
|
+
...cb.payload,
|
|
176
|
+
operation: cb.operation,
|
|
177
|
+
});
|
|
178
|
+
return { id: cb.id, result: callbackResult };
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return await this.resumeWithBatchResults(pausedResult.executionId, batchResults);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Handles a paused execution by processing the callback and resuming.
|
|
187
|
+
*/
|
|
188
|
+
private async handlePauseAndResume(pausedResult: ExecutionResult): Promise<ExecutionResult> {
|
|
189
|
+
if (!pausedResult.needsCallback) {
|
|
190
|
+
throw new Error('No callback request in paused execution');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const callbackResult = await this.serviceProviders.handleCallback(
|
|
195
|
+
pausedResult.needsCallback.type,
|
|
196
|
+
{
|
|
197
|
+
...pausedResult.needsCallback.payload,
|
|
198
|
+
operation: pausedResult.needsCallback.operation,
|
|
199
|
+
executionId: pausedResult.executionId,
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return await this.resume(pausedResult.executionId, callbackResult);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof ClientCallbackError) {
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
return await this.resume(pausedResult.executionId, {
|
|
209
|
+
__error: true,
|
|
210
|
+
message: error instanceof Error ? error.message : String(error),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resumes a paused execution with a callback result.
|
|
217
|
+
*/
|
|
218
|
+
async resume(executionId: string, callbackResult: unknown): Promise<ExecutionResult> {
|
|
219
|
+
await this.session.ensureInitialized();
|
|
220
|
+
|
|
221
|
+
const url = `${this.session.getBaseUrl()}/api/resume/${executionId}`;
|
|
222
|
+
const body = JSON.stringify({ result: callbackResult });
|
|
223
|
+
const headers = await this.session.prepareHeaders('POST', url, body);
|
|
224
|
+
|
|
225
|
+
const response = await fetch(url, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers,
|
|
228
|
+
body,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.session.updateToken(response);
|
|
232
|
+
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
const error = (await response.json()) as { error: string };
|
|
235
|
+
throw new Error(`Resume failed: ${error.error || response.statusText}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const result = (await response.json()) as ExecutionResult;
|
|
239
|
+
|
|
240
|
+
if (result.provenanceTokens && result.provenanceTokens.length > 0) {
|
|
241
|
+
for (const { token } of result.provenanceTokens) {
|
|
242
|
+
this.tokenRegistry.add(token);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallbacks) {
|
|
247
|
+
return await this.handleBatchCallbacksAndResume(result);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallback) {
|
|
251
|
+
return await this.handlePauseAndResume(result);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Resumes a paused execution with batch callback results.
|
|
259
|
+
*/
|
|
260
|
+
private async resumeWithBatchResults(
|
|
261
|
+
executionId: string,
|
|
262
|
+
batchResults: Array<{ id: string; result: unknown }>
|
|
263
|
+
): Promise<ExecutionResult> {
|
|
264
|
+
await this.session.ensureInitialized();
|
|
265
|
+
|
|
266
|
+
const url = `${this.session.getBaseUrl()}/api/resume/${executionId}`;
|
|
267
|
+
const body = JSON.stringify({ results: batchResults });
|
|
268
|
+
const headers = await this.session.prepareHeaders('POST', url, body);
|
|
269
|
+
|
|
270
|
+
const response = await fetch(url, {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers,
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
this.session.updateToken(response);
|
|
277
|
+
|
|
278
|
+
if (!response.ok) {
|
|
279
|
+
const error = (await response.json()) as { error: string };
|
|
280
|
+
throw new Error(`Batch resume failed: ${error.error || response.statusText}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = (await response.json()) as ExecutionResult;
|
|
284
|
+
|
|
285
|
+
if (result.provenanceTokens && result.provenanceTokens.length > 0) {
|
|
286
|
+
for (const { token } of result.provenanceTokens) {
|
|
287
|
+
this.tokenRegistry.add(token);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallbacks) {
|
|
292
|
+
return await this.handleBatchCallbacksAndResume(result);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (result.status === ExecutionStatus.PAUSED && result.needsCallback) {
|
|
296
|
+
return await this.handlePauseAndResume(result);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './session.js';
|
|
2
|
+
export * from './api-operations.js';
|
|
3
|
+
export * from './execution-operations.js';
|
|
4
|
+
export * from './service-providers.js';
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
|
|
7
|
+
export { CallbackType } from '@mondaydotcomorg/atp-protocol';
|
|
8
|
+
export type {
|
|
9
|
+
ClientLLMHandler,
|
|
10
|
+
ClientApprovalHandler,
|
|
11
|
+
ClientEmbeddingHandler,
|
|
12
|
+
ClientServiceProviders,
|
|
13
|
+
} from '@mondaydotcomorg/atp-protocol';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance Token Registry for Client
|
|
3
|
+
*
|
|
4
|
+
* Stores and manages provenance tokens for multi-step tracking
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TokenEntry {
|
|
8
|
+
token: string;
|
|
9
|
+
addedAt: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ProvenanceTokenRegistry {
|
|
13
|
+
private cache: Map<string, TokenEntry> = new Map();
|
|
14
|
+
private maxSize: number;
|
|
15
|
+
private ttl: number;
|
|
16
|
+
|
|
17
|
+
constructor(maxSize: number = 10000, ttlHours: number = 1) {
|
|
18
|
+
this.maxSize = maxSize;
|
|
19
|
+
this.ttl = ttlHours * 3600 * 1000;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Add a token to the registry
|
|
24
|
+
*/
|
|
25
|
+
add(token: string): void {
|
|
26
|
+
// Evict expired tokens first
|
|
27
|
+
this.evictExpired();
|
|
28
|
+
|
|
29
|
+
// Check if at capacity and evict LRU if needed
|
|
30
|
+
if (this.cache.size >= this.maxSize) {
|
|
31
|
+
this.evictLRU();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Store token
|
|
35
|
+
this.cache.set(token, {
|
|
36
|
+
token,
|
|
37
|
+
addedAt: Date.now(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get recent tokens (non-expired, sorted by age, limited)
|
|
43
|
+
*/
|
|
44
|
+
getRecentTokens(maxCount: number = 1000): string[] {
|
|
45
|
+
this.evictExpired();
|
|
46
|
+
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const expiredTokens: string[] = [];
|
|
49
|
+
|
|
50
|
+
const entries = Array.from(this.cache.values())
|
|
51
|
+
.filter((entry) => {
|
|
52
|
+
try {
|
|
53
|
+
const [body] = entry.token.split('.');
|
|
54
|
+
if (!body) {
|
|
55
|
+
expiredTokens.push(entry.token);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const payload = JSON.parse(Buffer.from(body, 'base64url').toString());
|
|
59
|
+
if (!payload.expiresAt || payload.expiresAt <= now) {
|
|
60
|
+
expiredTokens.push(entry.token);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
expiredTokens.push(entry.token);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.sort((a, b) => b.addedAt - a.addedAt)
|
|
70
|
+
.slice(0, maxCount);
|
|
71
|
+
|
|
72
|
+
for (const token of expiredTokens) {
|
|
73
|
+
this.cache.delete(token);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return entries.map((e) => e.token);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Clear all tokens
|
|
81
|
+
*/
|
|
82
|
+
clear(): void {
|
|
83
|
+
this.cache.clear();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get registry size
|
|
88
|
+
*/
|
|
89
|
+
size(): number {
|
|
90
|
+
return this.cache.size;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Evict expired tokens
|
|
95
|
+
*/
|
|
96
|
+
private evictExpired(): void {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
const toDelete: string[] = [];
|
|
99
|
+
|
|
100
|
+
for (const [token, entry] of this.cache.entries()) {
|
|
101
|
+
if (now - entry.addedAt > this.ttl) {
|
|
102
|
+
toDelete.push(token);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const token of toDelete) {
|
|
107
|
+
this.cache.delete(token);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Evict least recently used (oldest) token
|
|
113
|
+
*/
|
|
114
|
+
private evictLRU(): void {
|
|
115
|
+
let oldestToken: string | null = null;
|
|
116
|
+
let oldestTime = Infinity;
|
|
117
|
+
|
|
118
|
+
for (const [token, entry] of this.cache.entries()) {
|
|
119
|
+
if (entry.addedAt < oldestTime) {
|
|
120
|
+
oldestTime = entry.addedAt;
|
|
121
|
+
oldestToken = token;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (oldestToken) {
|
|
126
|
+
this.cache.delete(oldestToken);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { CallbackType, ToolOperation } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
import type {
|
|
3
|
+
ClientLLMHandler,
|
|
4
|
+
ClientApprovalHandler,
|
|
5
|
+
ClientEmbeddingHandler,
|
|
6
|
+
ClientServiceProviders,
|
|
7
|
+
ClientTool,
|
|
8
|
+
ClientToolDefinition,
|
|
9
|
+
ClientToolHandler,
|
|
10
|
+
} from '@mondaydotcomorg/atp-protocol';
|
|
11
|
+
|
|
12
|
+
const LLMOperation = {
|
|
13
|
+
CALL: 'call',
|
|
14
|
+
EXTRACT: 'extract',
|
|
15
|
+
CLASSIFY: 'classify',
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
const EmbeddingOperation = {
|
|
19
|
+
EMBED: 'embed',
|
|
20
|
+
SEARCH: 'search',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export class ServiceProviders {
|
|
24
|
+
private providers: ClientServiceProviders = {};
|
|
25
|
+
private toolHandlers: Map<string, ClientToolHandler> = new Map();
|
|
26
|
+
|
|
27
|
+
constructor(providers?: ClientServiceProviders) {
|
|
28
|
+
this.providers = providers || {};
|
|
29
|
+
|
|
30
|
+
if (providers?.tools) {
|
|
31
|
+
for (const tool of providers.tools) {
|
|
32
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
provideLLM(handler: ClientLLMHandler): void {
|
|
38
|
+
this.providers.llm = handler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
provideApproval(handler: ClientApprovalHandler): void {
|
|
42
|
+
this.providers.approval = handler;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
provideEmbedding(handler: ClientEmbeddingHandler): void {
|
|
46
|
+
this.providers.embedding = handler;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
provideTools(tools: ClientTool[]): void {
|
|
50
|
+
this.providers.tools = tools;
|
|
51
|
+
for (const tool of tools) {
|
|
52
|
+
this.toolHandlers.set(tool.name, tool.handler);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getLLM(): ClientLLMHandler | undefined {
|
|
57
|
+
return this.providers.llm;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getApproval(): ClientApprovalHandler | undefined {
|
|
61
|
+
return this.providers.approval;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getEmbedding(): ClientEmbeddingHandler | undefined {
|
|
65
|
+
return this.providers.embedding;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getTools(): ClientTool[] | undefined {
|
|
69
|
+
return this.providers.tools;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get tool definitions (without handlers) for sending to server
|
|
74
|
+
*/
|
|
75
|
+
getToolDefinitions(): ClientToolDefinition[] {
|
|
76
|
+
if (!this.providers.tools) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return this.providers.tools.map((tool) => {
|
|
81
|
+
const { handler, ...definition } = tool;
|
|
82
|
+
return definition;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if client has tools
|
|
88
|
+
*/
|
|
89
|
+
hasTools(): boolean {
|
|
90
|
+
return !!(this.providers.tools && this.providers.tools.length > 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if client has any services or tools
|
|
95
|
+
*/
|
|
96
|
+
hasAnyServices(): boolean {
|
|
97
|
+
return !!(
|
|
98
|
+
this.providers.llm ||
|
|
99
|
+
this.providers.approval ||
|
|
100
|
+
this.providers.embedding ||
|
|
101
|
+
this.hasTools()
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async handleCallback(callbackType: CallbackType, payload: any): Promise<any> {
|
|
106
|
+
if (payload.operation === 'batch_parallel' && payload.calls) {
|
|
107
|
+
return await Promise.all(
|
|
108
|
+
payload.calls.map(async (call: any) => {
|
|
109
|
+
return await this.handleCallback(call.type, {
|
|
110
|
+
...call.payload,
|
|
111
|
+
operation: call.operation,
|
|
112
|
+
});
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
switch (callbackType) {
|
|
118
|
+
case CallbackType.LLM:
|
|
119
|
+
if (!this.providers.llm) {
|
|
120
|
+
throw new Error('LLM service not provided by client');
|
|
121
|
+
}
|
|
122
|
+
if (payload.operation === LLMOperation.CALL) {
|
|
123
|
+
return await this.providers.llm.call(payload.prompt, payload.options);
|
|
124
|
+
} else if (payload.operation === LLMOperation.EXTRACT && this.providers.llm.extract) {
|
|
125
|
+
return await this.providers.llm.extract(payload.prompt, payload.schema, payload.options);
|
|
126
|
+
} else if (payload.operation === LLMOperation.CLASSIFY && this.providers.llm.classify) {
|
|
127
|
+
return await this.providers.llm.classify(
|
|
128
|
+
payload.text,
|
|
129
|
+
payload.categories,
|
|
130
|
+
payload.options
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Unsupported LLM operation: ${payload.operation}`);
|
|
134
|
+
|
|
135
|
+
case CallbackType.APPROVAL:
|
|
136
|
+
if (!this.providers.approval) {
|
|
137
|
+
throw new Error('Approval service not provided by client');
|
|
138
|
+
}
|
|
139
|
+
const contextWithExecutionId = payload.context
|
|
140
|
+
? { ...payload.context, executionId: payload.executionId }
|
|
141
|
+
: { executionId: payload.executionId };
|
|
142
|
+
return await this.providers.approval.request(payload.message, contextWithExecutionId);
|
|
143
|
+
|
|
144
|
+
case CallbackType.EMBEDDING:
|
|
145
|
+
if (!this.providers.embedding) {
|
|
146
|
+
throw new Error('Embedding service not provided by client');
|
|
147
|
+
}
|
|
148
|
+
if (payload.operation === EmbeddingOperation.EMBED) {
|
|
149
|
+
return await this.providers.embedding.embed(payload.text);
|
|
150
|
+
} else if (payload.operation === EmbeddingOperation.SEARCH) {
|
|
151
|
+
const queryEmbedding = await this.providers.embedding.embed(payload.query);
|
|
152
|
+
return queryEmbedding;
|
|
153
|
+
} else if (payload.operation === 'similarity' && this.providers.embedding.similarity) {
|
|
154
|
+
return await this.providers.embedding.similarity(payload.text1, payload.text2);
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Unsupported embedding operation: ${payload.operation}`);
|
|
157
|
+
|
|
158
|
+
case CallbackType.TOOL:
|
|
159
|
+
if (payload.operation === ToolOperation.CALL) {
|
|
160
|
+
const toolName = payload.toolName;
|
|
161
|
+
const handler = this.toolHandlers.get(toolName);
|
|
162
|
+
|
|
163
|
+
if (!handler) {
|
|
164
|
+
throw new Error(`Tool '${toolName}' not found in client tools`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = await handler(payload.input);
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`Unsupported tool operation: ${payload.operation}`);
|
|
171
|
+
|
|
172
|
+
default:
|
|
173
|
+
throw new Error(`Unknown callback type: ${callbackType}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|