@agentica/core 0.22.0 → 0.24.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 +11 -12
- package/lib/Agentica.d.ts +6 -1
- package/lib/Agentica.js +8 -3
- package/lib/Agentica.js.map +1 -1
- package/lib/MicroAgentica.d.ts +10 -0
- package/lib/MicroAgentica.js +11 -0
- package/lib/MicroAgentica.js.map +1 -1
- package/lib/context/AgenticaContext.d.ts +4 -0
- package/lib/functional/assertHttpController.d.ts +75 -0
- package/lib/functional/assertHttpController.js +9622 -0
- package/lib/functional/assertHttpController.js.map +1 -0
- package/lib/functional/assertHttpLlmApplication.d.ts +1 -0
- package/lib/functional/assertHttpLlmApplication.js +1 -0
- package/lib/functional/assertHttpLlmApplication.js.map +1 -1
- package/lib/functional/assertMcpController.d.ts +1 -1
- package/lib/functional/assertMcpController.js +1 -1
- package/lib/functional/assertMcpController.js.map +1 -1
- package/lib/functional/validateHttpController.d.ts +75 -0
- package/lib/functional/validateHttpController.js +7952 -0
- package/lib/functional/validateHttpController.js.map +1 -0
- package/lib/functional/validateHttpLlmApplication.d.ts +1 -0
- package/lib/functional/validateHttpLlmApplication.js +1 -0
- package/lib/functional/validateHttpLlmApplication.js.map +1 -1
- package/lib/functional/validateMcpController.d.ts +24 -0
- package/lib/functional/validateMcpController.js +3034 -0
- package/lib/functional/validateMcpController.js.map +1 -0
- package/lib/histories/AgenticaUserInputHistory.d.ts +13 -7
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +25981 -5985
- package/lib/index.mjs.map +1 -1
- package/lib/utils/ChatGptCompletionMessageUtil.spec.d.ts +1 -0
- package/lib/utils/ChatGptCompletionMessageUtil.spec.js +288 -0
- package/lib/utils/ChatGptCompletionMessageUtil.spec.js.map +1 -0
- package/lib/utils/ChatGptTokenUsageAggregator.spec.d.ts +1 -0
- package/lib/utils/ChatGptTokenUsageAggregator.spec.js +199 -0
- package/lib/utils/ChatGptTokenUsageAggregator.spec.js.map +1 -0
- package/lib/utils/Singleton.js +18 -0
- package/lib/utils/Singleton.js.map +1 -1
- package/lib/utils/Singleton.spec.d.ts +1 -0
- package/lib/utils/Singleton.spec.js +106 -0
- package/lib/utils/Singleton.spec.js.map +1 -0
- package/lib/utils/__map_take.spec.d.ts +1 -0
- package/lib/utils/__map_take.spec.js +108 -0
- package/lib/utils/__map_take.spec.js.map +1 -0
- package/package.json +1 -1
- package/src/Agentica.ts +16 -2
- package/src/MicroAgentica.ts +13 -0
- package/src/context/AgenticaContext.ts +5 -0
- package/src/functional/assertHttpController.ts +112 -0
- package/src/functional/assertHttpLlmApplication.ts +1 -0
- package/src/functional/assertMcpController.ts +1 -2
- package/src/functional/validateHttpController.ts +118 -0
- package/src/functional/validateHttpLlmApplication.ts +1 -1
- package/src/functional/validateMcpController.ts +56 -0
- package/src/histories/AgenticaUserInputHistory.ts +14 -8
- package/src/index.ts +3 -0
- package/src/utils/ChatGptCompletionMessageUtil.spec.ts +320 -0
- package/src/utils/ChatGptTokenUsageAggregator.spec.ts +226 -0
- package/src/utils/Singleton.spec.ts +138 -0
- package/src/utils/Singleton.ts +18 -0
- package/src/utils/__map_take.spec.ts +140 -0
- package/lib/utils/MathUtil.d.ts +0 -3
- package/lib/utils/MathUtil.js +0 -8
- package/lib/utils/MathUtil.js.map +0 -1
- package/src/utils/MathUtil.ts +0 -3
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChatCompletion,
|
|
3
|
+
ChatCompletionChunk,
|
|
4
|
+
ChatCompletionMessageToolCall,
|
|
5
|
+
} from "openai/resources";
|
|
6
|
+
|
|
7
|
+
import { ChatGptCompletionMessageUtil } from "./ChatGptCompletionMessageUtil";
|
|
8
|
+
|
|
9
|
+
describe("chatGptCompletionMessageUtil", () => {
|
|
10
|
+
describe("transformCompletionChunk", () => {
|
|
11
|
+
it("should transform string chunk to ChatCompletionChunk", () => {
|
|
12
|
+
const chunk = {
|
|
13
|
+
id: "test-id",
|
|
14
|
+
choices: [{
|
|
15
|
+
index: 0,
|
|
16
|
+
delta: { content: "Hello" },
|
|
17
|
+
}],
|
|
18
|
+
created: 1234567890,
|
|
19
|
+
model: "gpt-4",
|
|
20
|
+
object: "chat.completion.chunk",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const result = ChatGptCompletionMessageUtil.transformCompletionChunk(JSON.stringify(chunk));
|
|
24
|
+
expect(result).toEqual(chunk);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should transform Uint8Array chunk to ChatCompletionChunk", () => {
|
|
28
|
+
const chunk = {
|
|
29
|
+
id: "test-id",
|
|
30
|
+
choices: [{
|
|
31
|
+
index: 0,
|
|
32
|
+
delta: { content: "Hello" },
|
|
33
|
+
}],
|
|
34
|
+
created: 1234567890,
|
|
35
|
+
model: "gpt-4",
|
|
36
|
+
object: "chat.completion.chunk",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const uint8Array = new TextEncoder().encode(JSON.stringify(chunk));
|
|
40
|
+
const result = ChatGptCompletionMessageUtil.transformCompletionChunk(uint8Array);
|
|
41
|
+
expect(result).toEqual(chunk);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should handle invalid JSON", () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
ChatGptCompletionMessageUtil.transformCompletionChunk("invalid json");
|
|
47
|
+
}).toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("accumulate", () => {
|
|
52
|
+
it("should accumulate content from chunks", () => {
|
|
53
|
+
const origin: ChatCompletion = {
|
|
54
|
+
id: "test-id",
|
|
55
|
+
choices: [{
|
|
56
|
+
index: 0,
|
|
57
|
+
// @ts-expect-error - refusal is not required
|
|
58
|
+
message: { role: "assistant", content: "Hello" },
|
|
59
|
+
}],
|
|
60
|
+
created: 1234567890,
|
|
61
|
+
model: "gpt-4",
|
|
62
|
+
object: "chat.completion",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const chunk: ChatCompletionChunk = {
|
|
66
|
+
id: "test-id",
|
|
67
|
+
// @ts-expect-error - finish_reason is not required
|
|
68
|
+
choices: [{
|
|
69
|
+
index: 0,
|
|
70
|
+
delta: { content: " World" },
|
|
71
|
+
}],
|
|
72
|
+
created: 1234567890,
|
|
73
|
+
model: "gpt-4",
|
|
74
|
+
object: "chat.completion.chunk",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
|
|
78
|
+
expect(result.choices[0]?.message.content).toBe("Hello World");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should accumulate tool calls", () => {
|
|
82
|
+
const origin: ChatCompletion = {
|
|
83
|
+
id: "test-id",
|
|
84
|
+
choices: [{
|
|
85
|
+
index: 0,
|
|
86
|
+
// @ts-expect-error - finish_reason is not required
|
|
87
|
+
message: {
|
|
88
|
+
role: "assistant",
|
|
89
|
+
content: null,
|
|
90
|
+
tool_calls: [{
|
|
91
|
+
id: "call_1",
|
|
92
|
+
type: "function",
|
|
93
|
+
function: {
|
|
94
|
+
name: "test",
|
|
95
|
+
arguments: "{\"arg\": \"value\"}",
|
|
96
|
+
},
|
|
97
|
+
}],
|
|
98
|
+
},
|
|
99
|
+
}],
|
|
100
|
+
created: 1234567890,
|
|
101
|
+
model: "gpt-4",
|
|
102
|
+
object: "chat.completion",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const chunk: ChatCompletionChunk = {
|
|
106
|
+
id: "test-id",
|
|
107
|
+
// @ts-expect-error - finish_reason is not required
|
|
108
|
+
choices: [{
|
|
109
|
+
index: 0,
|
|
110
|
+
delta: {
|
|
111
|
+
tool_calls: [{
|
|
112
|
+
index: 0,
|
|
113
|
+
id: "call_1",
|
|
114
|
+
function: {
|
|
115
|
+
name: "_function",
|
|
116
|
+
arguments: "{\"arg2\": \"value2\"}",
|
|
117
|
+
},
|
|
118
|
+
}],
|
|
119
|
+
},
|
|
120
|
+
}],
|
|
121
|
+
created: 1234567890,
|
|
122
|
+
model: "gpt-4",
|
|
123
|
+
object: "chat.completion.chunk",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
|
|
127
|
+
expect(result.choices[0]?.message.tool_calls?.[0]?.function.name).toBe("test_function");
|
|
128
|
+
expect(result.choices[0]?.message.tool_calls?.[0]?.function.arguments).toBe("{\"arg\": \"value\"}{\"arg2\": \"value2\"}");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should handle usage aggregation", () => {
|
|
132
|
+
const origin: ChatCompletion = {
|
|
133
|
+
id: "test-id",
|
|
134
|
+
choices: [{
|
|
135
|
+
index: 0,
|
|
136
|
+
// @ts-expect-error - finish_reason is not required
|
|
137
|
+
message: { role: "assistant", content: "Hello" },
|
|
138
|
+
}],
|
|
139
|
+
created: 1234567890,
|
|
140
|
+
model: "gpt-4",
|
|
141
|
+
object: "chat.completion",
|
|
142
|
+
usage: {
|
|
143
|
+
prompt_tokens: 10,
|
|
144
|
+
completion_tokens: 5,
|
|
145
|
+
total_tokens: 15,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const chunk: ChatCompletionChunk = {
|
|
150
|
+
id: "test-id",
|
|
151
|
+
// @ts-expect-error - finish_reason is not required
|
|
152
|
+
choices: [{
|
|
153
|
+
index: 0,
|
|
154
|
+
delta: { content: " World" },
|
|
155
|
+
}],
|
|
156
|
+
created: 1234567890,
|
|
157
|
+
model: "gpt-4",
|
|
158
|
+
object: "chat.completion.chunk",
|
|
159
|
+
usage: {
|
|
160
|
+
prompt_tokens: 0,
|
|
161
|
+
completion_tokens: 6,
|
|
162
|
+
total_tokens: 6,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
|
|
167
|
+
expect(result.usage).toEqual({
|
|
168
|
+
prompt_tokens: 10,
|
|
169
|
+
completion_tokens: 11,
|
|
170
|
+
total_tokens: 21,
|
|
171
|
+
completion_tokens_details: {
|
|
172
|
+
accepted_prediction_tokens: 0,
|
|
173
|
+
reasoning_tokens: 0,
|
|
174
|
+
rejected_prediction_tokens: 0,
|
|
175
|
+
},
|
|
176
|
+
prompt_tokens_details: {
|
|
177
|
+
audio_tokens: 0,
|
|
178
|
+
cached_tokens: 0,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("merge", () => {
|
|
185
|
+
it("should merge multiple chunks into completion", () => {
|
|
186
|
+
const chunks: ChatCompletionChunk[] = [
|
|
187
|
+
{
|
|
188
|
+
id: "test-id",
|
|
189
|
+
// @ts-expect-error - finish_reason is not required
|
|
190
|
+
choices: [{
|
|
191
|
+
index: 0,
|
|
192
|
+
delta: { content: "Hello" },
|
|
193
|
+
}],
|
|
194
|
+
created: 1234567890,
|
|
195
|
+
model: "gpt-4",
|
|
196
|
+
object: "chat.completion.chunk",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "test-id",
|
|
200
|
+
// @ts-expect-error - finish_reason is not required
|
|
201
|
+
choices: [{
|
|
202
|
+
index: 0,
|
|
203
|
+
delta: { content: " World" },
|
|
204
|
+
}],
|
|
205
|
+
created: 1234567890,
|
|
206
|
+
model: "gpt-4",
|
|
207
|
+
object: "chat.completion.chunk",
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const result = ChatGptCompletionMessageUtil.merge(chunks);
|
|
212
|
+
expect(result.choices[0]?.message.content).toBe("Hello World");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should throw error for empty chunks array", () => {
|
|
216
|
+
expect(() => {
|
|
217
|
+
ChatGptCompletionMessageUtil.merge([]);
|
|
218
|
+
}).toThrow("No chunks received");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("mergeChoice", () => {
|
|
223
|
+
it("should merge finish reason", () => {
|
|
224
|
+
const acc: ChatCompletion.Choice = {
|
|
225
|
+
index: 0,
|
|
226
|
+
// @ts-expect-error - finish_reason is not required
|
|
227
|
+
message: { role: "assistant", content: "Hello" },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const cur: ChatCompletionChunk.Choice = {
|
|
231
|
+
index: 0,
|
|
232
|
+
delta: {},
|
|
233
|
+
finish_reason: "stop",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
|
|
237
|
+
expect(result.finish_reason).toBe("stop");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should merge content", () => {
|
|
241
|
+
const acc: ChatCompletion.Choice = {
|
|
242
|
+
index: 0,
|
|
243
|
+
// @ts-expect-error - refusal is not required
|
|
244
|
+
message: { role: "assistant", content: "Hello" },
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// @ts-expect-error - finish_reason is not required
|
|
248
|
+
const cur: ChatCompletionChunk.Choice = {
|
|
249
|
+
index: 0,
|
|
250
|
+
delta: { content: " World" },
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
|
|
254
|
+
expect(result.message.content).toBe("Hello World");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should merge refusal", () => {
|
|
258
|
+
// @ts-expect-error - finish_reason is not required
|
|
259
|
+
const acc: ChatCompletion.Choice = {
|
|
260
|
+
index: 0,
|
|
261
|
+
message: { role: "assistant", content: null, refusal: "I cannot" },
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// @ts-expect-error - finish_reason is not required
|
|
265
|
+
const cur: ChatCompletionChunk.Choice = {
|
|
266
|
+
index: 0,
|
|
267
|
+
delta: { refusal: " do that" },
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
|
|
271
|
+
expect(result.message.refusal).toBe("I cannot do that");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("mergeToolCalls", () => {
|
|
276
|
+
it("should merge tool call function arguments", () => {
|
|
277
|
+
const acc: ChatCompletionMessageToolCall = {
|
|
278
|
+
id: "call_1",
|
|
279
|
+
type: "function",
|
|
280
|
+
function: {
|
|
281
|
+
name: "test",
|
|
282
|
+
arguments: "{\"arg\": \"value\"}",
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const cur: ChatCompletionChunk.Choice.Delta.ToolCall = {
|
|
287
|
+
index: 0,
|
|
288
|
+
id: "call_1",
|
|
289
|
+
function: {
|
|
290
|
+
arguments: "{\"arg2\": \"value2\"}",
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const result = ChatGptCompletionMessageUtil.mergeToolCalls(acc, cur);
|
|
295
|
+
expect(result.function.arguments).toBe("{\"arg\": \"value\"}{\"arg2\": \"value2\"}");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should merge tool call function name", () => {
|
|
299
|
+
const acc: ChatCompletionMessageToolCall = {
|
|
300
|
+
id: "call_1",
|
|
301
|
+
type: "function",
|
|
302
|
+
function: {
|
|
303
|
+
name: "test",
|
|
304
|
+
arguments: "",
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const cur: ChatCompletionChunk.Choice.Delta.ToolCall = {
|
|
309
|
+
index: 0,
|
|
310
|
+
id: "call_1",
|
|
311
|
+
function: {
|
|
312
|
+
name: "_function",
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = ChatGptCompletionMessageUtil.mergeToolCalls(acc, cur);
|
|
317
|
+
expect(result.function.name).toBe("test_function");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { CompletionUsage } from "openai/resources";
|
|
2
|
+
|
|
3
|
+
import { ChatGptTokenUsageAggregator } from "./ChatGptTokenUsageAggregator";
|
|
4
|
+
|
|
5
|
+
describe("chatGptTokenUsageAggregator", () => {
|
|
6
|
+
describe("sum", () => {
|
|
7
|
+
it("should sum basic token usage", () => {
|
|
8
|
+
const usage1: CompletionUsage = {
|
|
9
|
+
prompt_tokens: 10,
|
|
10
|
+
completion_tokens: 5,
|
|
11
|
+
total_tokens: 15,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const usage2: CompletionUsage = {
|
|
15
|
+
prompt_tokens: 20,
|
|
16
|
+
completion_tokens: 10,
|
|
17
|
+
total_tokens: 30,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
21
|
+
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
prompt_tokens: 30,
|
|
24
|
+
completion_tokens: 15,
|
|
25
|
+
total_tokens: 45,
|
|
26
|
+
completion_tokens_details: {
|
|
27
|
+
accepted_prediction_tokens: 0,
|
|
28
|
+
reasoning_tokens: 0,
|
|
29
|
+
rejected_prediction_tokens: 0,
|
|
30
|
+
},
|
|
31
|
+
prompt_tokens_details: {
|
|
32
|
+
audio_tokens: 0,
|
|
33
|
+
cached_tokens: 0,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should handle undefined values", () => {
|
|
39
|
+
const usage1: CompletionUsage = {
|
|
40
|
+
prompt_tokens: 10,
|
|
41
|
+
completion_tokens: 5,
|
|
42
|
+
total_tokens: 15,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const usage2: CompletionUsage = {
|
|
46
|
+
// @ts-expect-error - intended to be undefined
|
|
47
|
+
prompt_tokens: undefined,
|
|
48
|
+
// @ts-expect-error - intended to be undefined
|
|
49
|
+
completion_tokens: undefined,
|
|
50
|
+
// @ts-expect-error - intended to be undefined
|
|
51
|
+
total_tokens: undefined,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
prompt_tokens: 10,
|
|
58
|
+
completion_tokens: 5,
|
|
59
|
+
total_tokens: 15,
|
|
60
|
+
completion_tokens_details: {
|
|
61
|
+
accepted_prediction_tokens: 0,
|
|
62
|
+
reasoning_tokens: 0,
|
|
63
|
+
rejected_prediction_tokens: 0,
|
|
64
|
+
},
|
|
65
|
+
prompt_tokens_details: {
|
|
66
|
+
audio_tokens: 0,
|
|
67
|
+
cached_tokens: 0,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should sum completion token details", () => {
|
|
73
|
+
const usage1: CompletionUsage = {
|
|
74
|
+
prompt_tokens: 10,
|
|
75
|
+
completion_tokens: 5,
|
|
76
|
+
total_tokens: 15,
|
|
77
|
+
completion_tokens_details: {
|
|
78
|
+
accepted_prediction_tokens: 3,
|
|
79
|
+
reasoning_tokens: 1,
|
|
80
|
+
rejected_prediction_tokens: 1,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const usage2: CompletionUsage = {
|
|
85
|
+
prompt_tokens: 20,
|
|
86
|
+
completion_tokens: 10,
|
|
87
|
+
total_tokens: 30,
|
|
88
|
+
completion_tokens_details: {
|
|
89
|
+
accepted_prediction_tokens: 7,
|
|
90
|
+
reasoning_tokens: 2,
|
|
91
|
+
rejected_prediction_tokens: 1,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
96
|
+
|
|
97
|
+
expect(result.completion_tokens_details).toEqual({
|
|
98
|
+
accepted_prediction_tokens: 10,
|
|
99
|
+
reasoning_tokens: 3,
|
|
100
|
+
rejected_prediction_tokens: 2,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should handle undefined completion token details", () => {
|
|
105
|
+
const usage1: CompletionUsage = {
|
|
106
|
+
prompt_tokens: 10,
|
|
107
|
+
completion_tokens: 5,
|
|
108
|
+
total_tokens: 15,
|
|
109
|
+
completion_tokens_details: {
|
|
110
|
+
accepted_prediction_tokens: 3,
|
|
111
|
+
reasoning_tokens: 1,
|
|
112
|
+
rejected_prediction_tokens: 1,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const usage2: CompletionUsage = {
|
|
117
|
+
prompt_tokens: 20,
|
|
118
|
+
completion_tokens: 10,
|
|
119
|
+
total_tokens: 30,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
123
|
+
|
|
124
|
+
expect(result.completion_tokens_details).toEqual({
|
|
125
|
+
accepted_prediction_tokens: 3,
|
|
126
|
+
reasoning_tokens: 1,
|
|
127
|
+
rejected_prediction_tokens: 1,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should sum prompt token details", () => {
|
|
132
|
+
const usage1: CompletionUsage = {
|
|
133
|
+
prompt_tokens: 10,
|
|
134
|
+
completion_tokens: 5,
|
|
135
|
+
total_tokens: 15,
|
|
136
|
+
prompt_tokens_details: {
|
|
137
|
+
audio_tokens: 3,
|
|
138
|
+
cached_tokens: 2,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const usage2: CompletionUsage = {
|
|
143
|
+
prompt_tokens: 20,
|
|
144
|
+
completion_tokens: 10,
|
|
145
|
+
total_tokens: 30,
|
|
146
|
+
prompt_tokens_details: {
|
|
147
|
+
audio_tokens: 7,
|
|
148
|
+
cached_tokens: 3,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
153
|
+
|
|
154
|
+
expect(result.prompt_tokens_details).toEqual({
|
|
155
|
+
audio_tokens: 10,
|
|
156
|
+
cached_tokens: 5,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should handle undefined prompt token details", () => {
|
|
161
|
+
const usage1: CompletionUsage = {
|
|
162
|
+
prompt_tokens: 10,
|
|
163
|
+
completion_tokens: 5,
|
|
164
|
+
total_tokens: 15,
|
|
165
|
+
prompt_tokens_details: {
|
|
166
|
+
audio_tokens: 3,
|
|
167
|
+
cached_tokens: 2,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const usage2: CompletionUsage = {
|
|
172
|
+
prompt_tokens: 20,
|
|
173
|
+
completion_tokens: 10,
|
|
174
|
+
total_tokens: 30,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
178
|
+
|
|
179
|
+
expect(result.prompt_tokens_details).toEqual({
|
|
180
|
+
audio_tokens: 3,
|
|
181
|
+
cached_tokens: 2,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should handle all undefined values", () => {
|
|
186
|
+
const usage1: CompletionUsage = {
|
|
187
|
+
// @ts-expect-error - intended to be undefined
|
|
188
|
+
prompt_tokens: undefined,
|
|
189
|
+
// @ts-expect-error - intended to be undefined
|
|
190
|
+
completion_tokens: undefined,
|
|
191
|
+
// @ts-expect-error - intended to be undefined
|
|
192
|
+
total_tokens: undefined,
|
|
193
|
+
completion_tokens_details: undefined,
|
|
194
|
+
prompt_tokens_details: undefined,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const usage2: CompletionUsage = {
|
|
198
|
+
// @ts-expect-error - intended to be undefined
|
|
199
|
+
prompt_tokens: undefined,
|
|
200
|
+
// @ts-expect-error - intended to be undefined
|
|
201
|
+
completion_tokens: undefined,
|
|
202
|
+
// @ts-expect-error - intended to be undefined
|
|
203
|
+
total_tokens: undefined,
|
|
204
|
+
completion_tokens_details: undefined,
|
|
205
|
+
prompt_tokens_details: undefined,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
|
|
209
|
+
|
|
210
|
+
expect(result).toEqual({
|
|
211
|
+
prompt_tokens: 0,
|
|
212
|
+
completion_tokens: 0,
|
|
213
|
+
total_tokens: 0,
|
|
214
|
+
completion_tokens_details: {
|
|
215
|
+
accepted_prediction_tokens: 0,
|
|
216
|
+
reasoning_tokens: 0,
|
|
217
|
+
rejected_prediction_tokens: 0,
|
|
218
|
+
},
|
|
219
|
+
prompt_tokens_details: {
|
|
220
|
+
audio_tokens: 0,
|
|
221
|
+
cached_tokens: 0,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Singleton } from "./Singleton";
|
|
2
|
+
|
|
3
|
+
describe("singleton", () => {
|
|
4
|
+
describe("basic functionality", () => {
|
|
5
|
+
it("should create instance only once", () => {
|
|
6
|
+
const factory = () => ({ value: 42 });
|
|
7
|
+
const singleton = new Singleton(factory);
|
|
8
|
+
|
|
9
|
+
const instance1 = singleton.get();
|
|
10
|
+
const instance2 = singleton.get();
|
|
11
|
+
|
|
12
|
+
expect(instance1).toBe(instance2);
|
|
13
|
+
expect(instance1.value).toBe(42);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should create different instances for different singletons", () => {
|
|
17
|
+
const factory1 = () => ({ value: 42 });
|
|
18
|
+
const factory2 = () => ({ value: 24 });
|
|
19
|
+
|
|
20
|
+
const singleton1 = new Singleton(factory1);
|
|
21
|
+
const singleton2 = new Singleton(factory2);
|
|
22
|
+
|
|
23
|
+
const instance1 = singleton1.get();
|
|
24
|
+
const instance2 = singleton2.get();
|
|
25
|
+
|
|
26
|
+
expect(instance1).not.toBe(instance2);
|
|
27
|
+
expect(instance1.value).toBe(42);
|
|
28
|
+
expect(instance2.value).toBe(24);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("constructor arguments", () => {
|
|
33
|
+
it("should pass constructor arguments to factory", () => {
|
|
34
|
+
const factory = (value: number) => ({ value });
|
|
35
|
+
const singleton = new Singleton(factory);
|
|
36
|
+
|
|
37
|
+
const instance = singleton.get(42);
|
|
38
|
+
expect(instance.value).toBe(42);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should use same instance with same constructor arguments", () => {
|
|
42
|
+
const factory = (value: number) => ({ value });
|
|
43
|
+
const singleton = new Singleton(factory);
|
|
44
|
+
|
|
45
|
+
const instance1 = singleton.get(42);
|
|
46
|
+
const instance2 = singleton.get(42);
|
|
47
|
+
|
|
48
|
+
expect(instance1).toBe(instance2);
|
|
49
|
+
expect(instance1.value).toBe(42);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should return same instance even with different constructor arguments", () => {
|
|
53
|
+
const factory = (value: number) => ({ value });
|
|
54
|
+
const singleton = new Singleton(factory);
|
|
55
|
+
|
|
56
|
+
const instance1 = singleton.get(42);
|
|
57
|
+
const instance2 = singleton.get(24);
|
|
58
|
+
|
|
59
|
+
expect(instance1).toBe(instance2);
|
|
60
|
+
expect(instance1.value).toBe(42);
|
|
61
|
+
expect(instance2.value).toBe(42);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("complex object types", () => {
|
|
66
|
+
it("should handle complex objects", () => {
|
|
67
|
+
interface ComplexObject {
|
|
68
|
+
id: number;
|
|
69
|
+
data: { name: string; value: number };
|
|
70
|
+
timestamp: Date;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const factory = (id: number, name: string, value: number) => ({
|
|
74
|
+
id,
|
|
75
|
+
data: { name, value },
|
|
76
|
+
timestamp: new Date(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const singleton = new Singleton<ComplexObject, [number, string, number]>(factory);
|
|
80
|
+
const instance = singleton.get(1, "test", 42);
|
|
81
|
+
|
|
82
|
+
expect(instance.id).toBe(1);
|
|
83
|
+
expect(instance.data.name).toBe("test");
|
|
84
|
+
expect(instance.data.value).toBe(42);
|
|
85
|
+
expect(instance.timestamp).toBeInstanceOf(Date);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should maintain same complex object instance", () => {
|
|
89
|
+
const factory = () => ({
|
|
90
|
+
data: new Map<string, number>([["key", 42]]),
|
|
91
|
+
array: [1, 2, 3],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const singleton = new Singleton(factory);
|
|
95
|
+
const instance1 = singleton.get();
|
|
96
|
+
const instance2 = singleton.get();
|
|
97
|
+
|
|
98
|
+
expect(instance1).toBe(instance2);
|
|
99
|
+
expect(instance1.data).toBe(instance2.data);
|
|
100
|
+
expect(instance1.array).toBe(instance2.array);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("edge cases", () => {
|
|
105
|
+
it("should handle null factory return", () => {
|
|
106
|
+
const factory = () => null;
|
|
107
|
+
const singleton = new Singleton(factory);
|
|
108
|
+
|
|
109
|
+
const instance = singleton.get();
|
|
110
|
+
expect(instance).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should handle undefined factory return", () => {
|
|
114
|
+
const factory = () => undefined;
|
|
115
|
+
const singleton = new Singleton(factory);
|
|
116
|
+
|
|
117
|
+
const instance = singleton.get();
|
|
118
|
+
expect(instance).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should handle primitive values", () => {
|
|
122
|
+
const factory = () => 42;
|
|
123
|
+
const singleton = new Singleton(factory);
|
|
124
|
+
|
|
125
|
+
const instance = singleton.get();
|
|
126
|
+
expect(instance).toBe(42);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should handle factory throwing error", () => {
|
|
130
|
+
const factory = () => {
|
|
131
|
+
throw new Error("Factory error");
|
|
132
|
+
};
|
|
133
|
+
const singleton = new Singleton(factory);
|
|
134
|
+
|
|
135
|
+
expect(() => singleton.get()).toThrow("Factory error");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
package/src/utils/Singleton.ts
CHANGED
|
@@ -5,6 +5,24 @@ const NOT_MOUNTED_YET = {};
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @internal
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* A singleton class that creates a single instance of a class.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const singleton = new Singleton((name: string) => new SomeClass(name));
|
|
15
|
+
* const instance = singleton.get("test");
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* but next case is not work
|
|
19
|
+
* ```ts
|
|
20
|
+
* const singleton = new Singleton((name: string) => new SomeClass(name));
|
|
21
|
+
* const instance = singleton.get("test");
|
|
22
|
+
* const instance2 = singleton.get("test2");
|
|
23
|
+
*
|
|
24
|
+
* expect(instance).toBe(instance2); // true
|
|
25
|
+
* ```
|
|
8
26
|
*/
|
|
9
27
|
export class Singleton<T, Args extends any[] = []> {
|
|
10
28
|
private readonly closure_: (...args: Args) => T;
|