@agentuity/core 0.0.33 → 0.0.35
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/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/json.js +23 -0
- package/dist/json.js.map +1 -0
- package/dist/services/_util.js +107 -0
- package/dist/services/_util.js.map +1 -0
- package/dist/services/adapter.js +2 -0
- package/dist/services/adapter.js.map +1 -0
- package/dist/services/exception.js +8 -0
- package/dist/services/exception.js.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/keyvalue.js +85 -0
- package/dist/services/keyvalue.js.map +1 -0
- package/dist/services/objectstore.js +218 -0
- package/dist/services/objectstore.js.map +1 -0
- package/dist/services/stream.js +392 -0
- package/dist/services/stream.js.map +1 -0
- package/dist/services/vector.js +242 -0
- package/dist/services/vector.js.map +1 -0
- package/dist/standard_schema.js +2 -0
- package/dist/standard_schema.js.map +1 -0
- package/dist/typehelper.js +2 -0
- package/dist/typehelper.js.map +1 -0
- package/package.json +5 -4
- package/src/index.ts +4 -0
- package/src/json.ts +26 -0
- package/src/services/__test__/keyvalue.test.ts +402 -0
- package/src/services/__test__/mock-adapter.ts +114 -0
- package/src/services/__test__/objectstore.test.ts +431 -0
- package/src/services/__test__/stream.test.ts +554 -0
- package/src/services/__test__/vector.test.ts +813 -0
- package/src/services/_util.ts +117 -0
- package/src/services/adapter.ts +33 -0
- package/src/services/exception.ts +7 -0
- package/src/services/index.ts +7 -0
- package/src/services/keyvalue.ts +185 -0
- package/src/services/objectstore.ts +466 -0
- package/src/services/stream.ts +614 -0
- package/src/services/vector.ts +599 -0
- package/src/standard_schema.ts +69 -0
- package/src/typehelper.ts +5 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { safeStringify } from '../json';
|
|
2
|
+
import type { Body } from './adapter';
|
|
3
|
+
import { ServiceException } from './exception';
|
|
4
|
+
|
|
5
|
+
export const buildUrl = (
|
|
6
|
+
base: string,
|
|
7
|
+
path: string,
|
|
8
|
+
subpath?: string,
|
|
9
|
+
query?: URLSearchParams
|
|
10
|
+
): string => {
|
|
11
|
+
path = path.startsWith('/') ? path : `/${path}`;
|
|
12
|
+
let url = base.replace(/\/$/, '') + path;
|
|
13
|
+
if (subpath) {
|
|
14
|
+
subpath = subpath.startsWith('/') ? subpath : `/${subpath}`;
|
|
15
|
+
url += subpath;
|
|
16
|
+
}
|
|
17
|
+
if (query) {
|
|
18
|
+
url += `?${query.toString()}`;
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function toServiceException(response: Response): Promise<ServiceException> {
|
|
24
|
+
switch (response.status) {
|
|
25
|
+
case 401:
|
|
26
|
+
case 403:
|
|
27
|
+
return new ServiceException('Unauthorized', response.status);
|
|
28
|
+
case 404:
|
|
29
|
+
return new ServiceException('Not Found', response.status);
|
|
30
|
+
default:
|
|
31
|
+
}
|
|
32
|
+
const ct = response.headers.get('content-type');
|
|
33
|
+
if (ct?.includes('json')) {
|
|
34
|
+
try {
|
|
35
|
+
const payload = (await response.json()) as { message?: string; error?: string };
|
|
36
|
+
if (payload.error) {
|
|
37
|
+
return new ServiceException(payload.error, response.status);
|
|
38
|
+
}
|
|
39
|
+
if (payload.message) {
|
|
40
|
+
return new ServiceException(payload.message, response.status);
|
|
41
|
+
}
|
|
42
|
+
return new ServiceException(JSON.stringify(payload), response.status);
|
|
43
|
+
} catch {
|
|
44
|
+
/** don't worry */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const body = await response.text();
|
|
49
|
+
return new ServiceException(body, response.status);
|
|
50
|
+
} catch {
|
|
51
|
+
/* fall through */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new ServiceException(response.statusText, response.status);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const binaryContentType = 'application/octet-stream';
|
|
58
|
+
const textContentType = 'text/plain';
|
|
59
|
+
const jsonContentType = 'application/json';
|
|
60
|
+
|
|
61
|
+
export async function toPayload(data: unknown): Promise<[Body, string]> {
|
|
62
|
+
if (data === undefined || data === null) {
|
|
63
|
+
return ['', textContentType];
|
|
64
|
+
}
|
|
65
|
+
switch (typeof data) {
|
|
66
|
+
case 'string':
|
|
67
|
+
if (
|
|
68
|
+
(data.charAt(0) === '{' && data.charAt(data.length - 1) === '}') ||
|
|
69
|
+
(data.charAt(0) === '[' && data.charAt(data.length - 1) === ']')
|
|
70
|
+
) {
|
|
71
|
+
try {
|
|
72
|
+
JSON.parse(data);
|
|
73
|
+
return [data, jsonContentType];
|
|
74
|
+
} catch {
|
|
75
|
+
/* fall through */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [data, textContentType];
|
|
79
|
+
case 'boolean':
|
|
80
|
+
return [String(data), textContentType];
|
|
81
|
+
case 'number':
|
|
82
|
+
return [String(data), textContentType];
|
|
83
|
+
case 'object': {
|
|
84
|
+
if (data instanceof ArrayBuffer) {
|
|
85
|
+
return [data, binaryContentType];
|
|
86
|
+
}
|
|
87
|
+
if (data instanceof Buffer) {
|
|
88
|
+
return [data, binaryContentType];
|
|
89
|
+
}
|
|
90
|
+
if (data instanceof ReadableStream) {
|
|
91
|
+
return [data, binaryContentType];
|
|
92
|
+
}
|
|
93
|
+
if (data instanceof Promise) {
|
|
94
|
+
return toPayload(await data);
|
|
95
|
+
}
|
|
96
|
+
if (data instanceof Function) {
|
|
97
|
+
return toPayload(data());
|
|
98
|
+
}
|
|
99
|
+
return [safeStringify(data), jsonContentType];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return ['', textContentType];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function fromResponse<T>(response: Response): Promise<T> {
|
|
106
|
+
const contentType = response.headers.get('content-type');
|
|
107
|
+
if (!contentType || contentType?.includes('/json')) {
|
|
108
|
+
return (await response.json()) as T;
|
|
109
|
+
}
|
|
110
|
+
if (contentType?.includes('text/')) {
|
|
111
|
+
return (await response.text()) as T;
|
|
112
|
+
}
|
|
113
|
+
if (contentType?.includes(binaryContentType)) {
|
|
114
|
+
return (await response.arrayBuffer()) as T;
|
|
115
|
+
}
|
|
116
|
+
throw new ServiceException(`Unsupported content-type: ${contentType}`, response.status);
|
|
117
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface FetchSuccessResponse<TData> {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: TData;
|
|
4
|
+
response: Response;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FetchErrorResponse {
|
|
8
|
+
ok: false;
|
|
9
|
+
data: never;
|
|
10
|
+
response: Response;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type FetchResponse<T> = FetchErrorResponse | FetchSuccessResponse<T>;
|
|
14
|
+
|
|
15
|
+
export type Body = string | Buffer | ArrayBuffer | ReadableStream;
|
|
16
|
+
|
|
17
|
+
export interface FetchRequest {
|
|
18
|
+
method: 'GET' | 'PUT' | 'POST' | 'DELETE';
|
|
19
|
+
body?: Body;
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
contentType?: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
telemetry?: {
|
|
24
|
+
name: string;
|
|
25
|
+
attributes?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
binary?: true;
|
|
28
|
+
duplex?: 'half';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FetchAdapter {
|
|
32
|
+
invoke<T>(url: string, options: FetchRequest): Promise<FetchResponse<T>>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { FetchAdapter } from './adapter';
|
|
2
|
+
import { buildUrl, toServiceException, toPayload } from './_util';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* the result of a data operation when the data is found
|
|
6
|
+
*/
|
|
7
|
+
export interface DataResultFound<T> {
|
|
8
|
+
/**
|
|
9
|
+
* the data from the result of the operation
|
|
10
|
+
*/
|
|
11
|
+
data: T;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* the content type of the data
|
|
15
|
+
*/
|
|
16
|
+
contentType: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* the data was found
|
|
20
|
+
*/
|
|
21
|
+
exists: true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* the result of a data operation when the data is not found
|
|
26
|
+
*/
|
|
27
|
+
export interface DataResultNotFound {
|
|
28
|
+
data: never;
|
|
29
|
+
/**
|
|
30
|
+
* the data was not found
|
|
31
|
+
*/
|
|
32
|
+
exists: false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* the result of a data operation
|
|
37
|
+
*/
|
|
38
|
+
export type DataResult<T> = DataResultFound<T> | DataResultNotFound;
|
|
39
|
+
|
|
40
|
+
export interface KeyValueStorageSetParams {
|
|
41
|
+
/**
|
|
42
|
+
* the number of milliseconds to keep the value in the cache
|
|
43
|
+
*/
|
|
44
|
+
ttl?: number;
|
|
45
|
+
/**
|
|
46
|
+
* the content type of the value
|
|
47
|
+
*/
|
|
48
|
+
contentType?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface KeyValueStorage {
|
|
52
|
+
/**
|
|
53
|
+
* get a value from the key value storage
|
|
54
|
+
*
|
|
55
|
+
* @param name - the name of the key value storage
|
|
56
|
+
* @param key - the key to get the value of
|
|
57
|
+
* @returns the DataResult object
|
|
58
|
+
*/
|
|
59
|
+
get<T>(name: string, key: string): Promise<DataResult<T>>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* set a value in the key value storage
|
|
63
|
+
*
|
|
64
|
+
* @param name - the name of the key value storage
|
|
65
|
+
* @param key - the key to set the value of
|
|
66
|
+
* @param value - the value to set in any of the supported data types
|
|
67
|
+
* @param params - the KeyValueStorageSetParams
|
|
68
|
+
*/
|
|
69
|
+
set<T = unknown>(
|
|
70
|
+
name: string,
|
|
71
|
+
key: string,
|
|
72
|
+
value: T,
|
|
73
|
+
params?: KeyValueStorageSetParams
|
|
74
|
+
): Promise<void>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* delete a value from the key value storage
|
|
78
|
+
*
|
|
79
|
+
* @param name - the name of the key value storage
|
|
80
|
+
* @param key - the key to delete
|
|
81
|
+
*/
|
|
82
|
+
delete(name: string, key: string): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class KeyValueStorageService implements KeyValueStorage {
|
|
86
|
+
#adapter: FetchAdapter;
|
|
87
|
+
#baseUrl: string;
|
|
88
|
+
|
|
89
|
+
constructor(baseUrl: string, adapter: FetchAdapter) {
|
|
90
|
+
this.#adapter = adapter;
|
|
91
|
+
this.#baseUrl = baseUrl;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async get<T>(name: string, key: string): Promise<DataResult<T>> {
|
|
95
|
+
const url = buildUrl(
|
|
96
|
+
this.#baseUrl,
|
|
97
|
+
`/kv/2025-03-17/${encodeURIComponent(name)}/${encodeURIComponent(key)}`
|
|
98
|
+
);
|
|
99
|
+
const signal = AbortSignal.timeout(10_000);
|
|
100
|
+
const res = await this.#adapter.invoke<T>(url, {
|
|
101
|
+
method: 'GET',
|
|
102
|
+
signal,
|
|
103
|
+
telemetry: {
|
|
104
|
+
name: 'agentuity.keyvalue.get',
|
|
105
|
+
attributes: {
|
|
106
|
+
name,
|
|
107
|
+
key,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
if (res.ok) {
|
|
112
|
+
return {
|
|
113
|
+
data: res.data,
|
|
114
|
+
contentType: res.response.headers.get('content-type') ?? 'application/octet-stream',
|
|
115
|
+
exists: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (res.response.status === 404) {
|
|
119
|
+
return { exists: false } as DataResultNotFound;
|
|
120
|
+
}
|
|
121
|
+
throw await toServiceException(res.response);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async set<T = unknown>(
|
|
125
|
+
name: string,
|
|
126
|
+
key: string,
|
|
127
|
+
value: T,
|
|
128
|
+
params?: KeyValueStorageSetParams
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
let ttlstr = '';
|
|
131
|
+
if (params?.ttl) {
|
|
132
|
+
if (params.ttl < 60) {
|
|
133
|
+
throw new Error(`ttl for keyvalue set must be at least 60 seconds, got ${params.ttl}`);
|
|
134
|
+
}
|
|
135
|
+
ttlstr = `/${params.ttl}`;
|
|
136
|
+
}
|
|
137
|
+
const url = buildUrl(
|
|
138
|
+
this.#baseUrl,
|
|
139
|
+
`/kv/2025-03-17/${encodeURIComponent(name)}/${encodeURIComponent(key)}${ttlstr}`
|
|
140
|
+
);
|
|
141
|
+
const [body, contentType] = await toPayload(value);
|
|
142
|
+
const signal = AbortSignal.timeout(30_000);
|
|
143
|
+
const res = await this.#adapter.invoke<T>(url, {
|
|
144
|
+
method: 'PUT',
|
|
145
|
+
signal,
|
|
146
|
+
body,
|
|
147
|
+
contentType: params?.contentType || contentType,
|
|
148
|
+
telemetry: {
|
|
149
|
+
name: 'agentuity.keyvalue.set',
|
|
150
|
+
attributes: {
|
|
151
|
+
name,
|
|
152
|
+
key,
|
|
153
|
+
ttl: ttlstr,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
if (res.ok) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
throw await toServiceException(res.response);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async delete(name: string, key: string): Promise<void> {
|
|
164
|
+
const url = buildUrl(
|
|
165
|
+
this.#baseUrl,
|
|
166
|
+
`/kv/2025-03-17/${encodeURIComponent(name)}/${encodeURIComponent(key)}`
|
|
167
|
+
);
|
|
168
|
+
const signal = AbortSignal.timeout(30_000);
|
|
169
|
+
const res = await this.#adapter.invoke(url, {
|
|
170
|
+
method: 'DELETE',
|
|
171
|
+
signal,
|
|
172
|
+
telemetry: {
|
|
173
|
+
name: 'agentuity.keyvalue.delete',
|
|
174
|
+
attributes: {
|
|
175
|
+
name,
|
|
176
|
+
key,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
if (res.ok) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
throw await toServiceException(res.response);
|
|
184
|
+
}
|
|
185
|
+
}
|