@argosvix/sdk 0.1.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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +887 -0
- package/dist/client.js.map +1 -0
- package/dist/flush.d.ts +29 -0
- package/dist/flush.d.ts.map +1 -0
- package/dist/flush.js +41 -0
- package/dist/flush.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pricing.d.ts +32 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +137 -0
- package/dist/pricing.js.map +1 -0
- package/dist/recorder.d.ts +31 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +100 -0
- package/dist/recorder.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
import { calculateCost } from "./pricing.js";
|
|
2
|
+
import { Recorder } from "./recorder.js";
|
|
3
|
+
const wrappedClients = new WeakMap();
|
|
4
|
+
const wrappedGeminiModels = new WeakSet();
|
|
5
|
+
/**
|
|
6
|
+
* Wrap an AI provider SDK client to transparently record every call.
|
|
7
|
+
*
|
|
8
|
+
* Supported: OpenAI (chat.completions + responses), Anthropic (messages),
|
|
9
|
+
* Mistral (chat.complete + chat.stream), Gemini (legacy `@google/generative-ai`
|
|
10
|
+
* + current `@google/genai`).
|
|
11
|
+
*
|
|
12
|
+
* Provider detection: constructor-name fingerprint with shape-based fallback.
|
|
13
|
+
* For composite or adapter clients, pass `config.provider` to override detection.
|
|
14
|
+
*
|
|
15
|
+
* Idempotency: wrapping the same client twice is a no-op.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import OpenAI from "openai";
|
|
19
|
+
* import { wrap, getRecorder } from "@argosvix/sdk";
|
|
20
|
+
*
|
|
21
|
+
* const client = wrap(new OpenAI(), { apiKey: "...", tags: { service: "bot" } });
|
|
22
|
+
* const recorder = getRecorder(client);
|
|
23
|
+
*/
|
|
24
|
+
export function wrap(client, config = {}) {
|
|
25
|
+
if (wrappedClients.has(client)) {
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
28
|
+
const provider = detectProvider(client, config.provider);
|
|
29
|
+
if (provider === "unknown") {
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
32
|
+
const recorder = new Recorder(config);
|
|
33
|
+
switch (provider) {
|
|
34
|
+
case "openai": {
|
|
35
|
+
if (isOpenAIChatLike(client))
|
|
36
|
+
wrapOpenAIChat(client, recorder, config);
|
|
37
|
+
if (isOpenAIResponsesLike(client))
|
|
38
|
+
wrapOpenAIResponses(client, recorder, config);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "anthropic": {
|
|
42
|
+
if (isAnthropicLike(client))
|
|
43
|
+
wrapAnthropic(client, recorder, config);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "mistral": {
|
|
47
|
+
if (isMistralLike(client))
|
|
48
|
+
wrapMistral(client, recorder, config);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "gemini": {
|
|
52
|
+
if (isGeminiLegacyLike(client))
|
|
53
|
+
wrapGeminiLegacy(client, recorder, config);
|
|
54
|
+
if (isGeminiNewLike(client))
|
|
55
|
+
wrapGeminiNew(client, recorder, config);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
wrappedClients.set(client, recorder);
|
|
60
|
+
return client;
|
|
61
|
+
}
|
|
62
|
+
export function getRecorder(client) {
|
|
63
|
+
return wrappedClients.get(client) ?? null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve provider. config.provider override has priority; otherwise
|
|
67
|
+
* constructor name fingerprint, with shape-based check as final fallback.
|
|
68
|
+
* Returns "unknown" if no provider can be reliably identified.
|
|
69
|
+
*/
|
|
70
|
+
function detectProvider(client, override) {
|
|
71
|
+
if (override)
|
|
72
|
+
return override;
|
|
73
|
+
const ctorName = (client.constructor?.name ?? "").toLowerCase();
|
|
74
|
+
if (ctorName.includes("openai"))
|
|
75
|
+
return "openai";
|
|
76
|
+
if (ctorName.includes("anthropic"))
|
|
77
|
+
return "anthropic";
|
|
78
|
+
if (ctorName.includes("mistral"))
|
|
79
|
+
return "mistral";
|
|
80
|
+
if (ctorName.includes("googlegenai") ||
|
|
81
|
+
ctorName.includes("googlegenerativeai") ||
|
|
82
|
+
ctorName === "genai" ||
|
|
83
|
+
ctorName === "ai") {
|
|
84
|
+
return "gemini";
|
|
85
|
+
}
|
|
86
|
+
// Shape fallback for cases where constructor name is unavailable (e.g. generic Object).
|
|
87
|
+
if (isGeminiLegacyLike(client) || isGeminiNewLike(client))
|
|
88
|
+
return "gemini";
|
|
89
|
+
if (isAnthropicLike(client) && !isOpenAIChatLike(client) && !isMistralLike(client)) {
|
|
90
|
+
return "anthropic";
|
|
91
|
+
}
|
|
92
|
+
if (isMistralLike(client) && !isOpenAIChatLike(client))
|
|
93
|
+
return "mistral";
|
|
94
|
+
if (isOpenAIChatLike(client) || isOpenAIResponsesLike(client))
|
|
95
|
+
return "openai";
|
|
96
|
+
return "unknown";
|
|
97
|
+
}
|
|
98
|
+
function extractErrorDetails(err) {
|
|
99
|
+
if (!err || typeof err !== "object")
|
|
100
|
+
return undefined;
|
|
101
|
+
const e = err;
|
|
102
|
+
const details = {};
|
|
103
|
+
const status = e.status ?? e.statusCode;
|
|
104
|
+
if (typeof status === "number")
|
|
105
|
+
details.statusCode = status;
|
|
106
|
+
if (typeof e.code === "string")
|
|
107
|
+
details.code = e.code;
|
|
108
|
+
if (typeof e.type === "string")
|
|
109
|
+
details.type = e.type;
|
|
110
|
+
if (typeof e.param === "string")
|
|
111
|
+
details.param = e.param;
|
|
112
|
+
const retryAfterHeader = e.headers?.["retry-after"] ?? e.headers?.["Retry-After"];
|
|
113
|
+
if (retryAfterHeader) {
|
|
114
|
+
const n = Number(retryAfterHeader);
|
|
115
|
+
if (!Number.isNaN(n))
|
|
116
|
+
details.retryAfter = n;
|
|
117
|
+
}
|
|
118
|
+
return Object.keys(details).length > 0 ? details : undefined;
|
|
119
|
+
}
|
|
120
|
+
function isOpenAIChatLike(client) {
|
|
121
|
+
const c = client;
|
|
122
|
+
return typeof c?.chat?.completions?.create === "function";
|
|
123
|
+
}
|
|
124
|
+
function wrapOpenAIChat(client, recorder, config) {
|
|
125
|
+
const c = client;
|
|
126
|
+
const originalCreate = c.chat.completions.create.bind(c.chat.completions);
|
|
127
|
+
c.chat.completions.create = async function (...args) {
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
const requestArgs = args[0] || {};
|
|
130
|
+
const id = generateId();
|
|
131
|
+
const isStream = requestArgs.stream === true;
|
|
132
|
+
try {
|
|
133
|
+
const response = await originalCreate(...args);
|
|
134
|
+
if (isStream) {
|
|
135
|
+
return wrapOpenAIStream(response, recorder, requestArgs, start, id, config);
|
|
136
|
+
}
|
|
137
|
+
const r = response;
|
|
138
|
+
const latencyMs = Date.now() - start;
|
|
139
|
+
const model = r.model || requestArgs.model || "unknown";
|
|
140
|
+
const promptTokens = r.usage?.prompt_tokens ?? 0;
|
|
141
|
+
const completionTokens = r.usage?.completion_tokens ?? 0;
|
|
142
|
+
recorder.record({
|
|
143
|
+
id,
|
|
144
|
+
provider: "openai",
|
|
145
|
+
model,
|
|
146
|
+
promptTokens,
|
|
147
|
+
completionTokens,
|
|
148
|
+
totalTokens: r.usage?.total_tokens ?? promptTokens + completionTokens,
|
|
149
|
+
costUsd: calculateCost("openai", model, promptTokens, completionTokens),
|
|
150
|
+
latencyMs,
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
tags: { ...(config.tags ?? {}) },
|
|
153
|
+
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
154
|
+
});
|
|
155
|
+
return response;
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const errorDetails = extractErrorDetails(err);
|
|
159
|
+
recorder.record({
|
|
160
|
+
id,
|
|
161
|
+
provider: "openai",
|
|
162
|
+
model: requestArgs.model || "unknown",
|
|
163
|
+
promptTokens: 0,
|
|
164
|
+
completionTokens: 0,
|
|
165
|
+
totalTokens: 0,
|
|
166
|
+
costUsd: 0,
|
|
167
|
+
latencyMs: Date.now() - start,
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
tags: { ...(config.tags ?? {}) },
|
|
170
|
+
error: err instanceof Error ? err.message : String(err),
|
|
171
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
172
|
+
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
173
|
+
});
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function* wrapOpenAIStream(stream, recorder, requestArgs, start, id, config) {
|
|
179
|
+
let finalModel = requestArgs.model || "unknown";
|
|
180
|
+
let promptTokens = 0;
|
|
181
|
+
let completionTokens = 0;
|
|
182
|
+
let reportedTotal;
|
|
183
|
+
try {
|
|
184
|
+
for await (const chunk of stream) {
|
|
185
|
+
if (chunk.model)
|
|
186
|
+
finalModel = chunk.model;
|
|
187
|
+
if (chunk.usage) {
|
|
188
|
+
promptTokens = chunk.usage.prompt_tokens ?? promptTokens;
|
|
189
|
+
completionTokens = chunk.usage.completion_tokens ?? completionTokens;
|
|
190
|
+
if (typeof chunk.usage.total_tokens === "number") {
|
|
191
|
+
reportedTotal = chunk.usage.total_tokens;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
yield chunk;
|
|
195
|
+
}
|
|
196
|
+
recorder.record({
|
|
197
|
+
id,
|
|
198
|
+
provider: "openai",
|
|
199
|
+
model: finalModel,
|
|
200
|
+
promptTokens,
|
|
201
|
+
completionTokens,
|
|
202
|
+
totalTokens: reportedTotal ?? promptTokens + completionTokens,
|
|
203
|
+
costUsd: calculateCost("openai", finalModel, promptTokens, completionTokens),
|
|
204
|
+
latencyMs: Date.now() - start,
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
tags: { ...(config.tags ?? {}) },
|
|
207
|
+
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const errorDetails = extractErrorDetails(err);
|
|
212
|
+
recorder.record({
|
|
213
|
+
id,
|
|
214
|
+
provider: "openai",
|
|
215
|
+
model: finalModel,
|
|
216
|
+
promptTokens,
|
|
217
|
+
completionTokens,
|
|
218
|
+
totalTokens: reportedTotal ?? promptTokens + completionTokens,
|
|
219
|
+
costUsd: calculateCost("openai", finalModel, promptTokens, completionTokens),
|
|
220
|
+
latencyMs: Date.now() - start,
|
|
221
|
+
timestamp: new Date().toISOString(),
|
|
222
|
+
tags: { ...(config.tags ?? {}) },
|
|
223
|
+
error: err instanceof Error ? err.message : String(err),
|
|
224
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
225
|
+
requestMeta: buildOpenAIRequestMeta(requestArgs),
|
|
226
|
+
});
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function buildOpenAIRequestMeta(requestArgs) {
|
|
231
|
+
const meta = {};
|
|
232
|
+
if (Array.isArray(requestArgs.messages))
|
|
233
|
+
meta.messagesCount = requestArgs.messages.length;
|
|
234
|
+
if (typeof requestArgs.temperature === "number")
|
|
235
|
+
meta.temperature = requestArgs.temperature;
|
|
236
|
+
const maxTokens = requestArgs.max_tokens ?? requestArgs.max_completion_tokens;
|
|
237
|
+
if (typeof maxTokens === "number")
|
|
238
|
+
meta.maxTokens = maxTokens;
|
|
239
|
+
return meta;
|
|
240
|
+
}
|
|
241
|
+
function isOpenAIResponsesLike(client) {
|
|
242
|
+
const c = client;
|
|
243
|
+
return typeof c?.responses?.create === "function";
|
|
244
|
+
}
|
|
245
|
+
function wrapOpenAIResponses(client, recorder, config) {
|
|
246
|
+
const c = client;
|
|
247
|
+
const originalCreate = c.responses.create.bind(c.responses);
|
|
248
|
+
c.responses.create = async function (...args) {
|
|
249
|
+
const start = Date.now();
|
|
250
|
+
const requestArgs = args[0] || {};
|
|
251
|
+
const id = generateId();
|
|
252
|
+
const isStream = requestArgs.stream === true;
|
|
253
|
+
if (isStream) {
|
|
254
|
+
// Note: responses API streaming support is deferred to a later phase.
|
|
255
|
+
// eslint-disable-next-line no-console
|
|
256
|
+
console.warn("[argosvix] responses.create with stream:true is not yet observed");
|
|
257
|
+
return originalCreate(...args);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const response = (await originalCreate(...args));
|
|
261
|
+
const latencyMs = Date.now() - start;
|
|
262
|
+
const model = response.model || requestArgs.model || "unknown";
|
|
263
|
+
const promptTokens = response.usage?.input_tokens ?? 0;
|
|
264
|
+
const completionTokens = response.usage?.output_tokens ?? 0;
|
|
265
|
+
recorder.record({
|
|
266
|
+
id,
|
|
267
|
+
provider: "openai",
|
|
268
|
+
model,
|
|
269
|
+
promptTokens,
|
|
270
|
+
completionTokens,
|
|
271
|
+
totalTokens: response.usage?.total_tokens ?? promptTokens + completionTokens,
|
|
272
|
+
costUsd: calculateCost("openai", model, promptTokens, completionTokens),
|
|
273
|
+
latencyMs,
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
tags: { ...(config.tags ?? {}) },
|
|
276
|
+
requestMeta: buildResponsesRequestMeta(requestArgs),
|
|
277
|
+
});
|
|
278
|
+
return response;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
const errorDetails = extractErrorDetails(err);
|
|
282
|
+
recorder.record({
|
|
283
|
+
id,
|
|
284
|
+
provider: "openai",
|
|
285
|
+
model: requestArgs.model || "unknown",
|
|
286
|
+
promptTokens: 0,
|
|
287
|
+
completionTokens: 0,
|
|
288
|
+
totalTokens: 0,
|
|
289
|
+
costUsd: 0,
|
|
290
|
+
latencyMs: Date.now() - start,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
tags: { ...(config.tags ?? {}) },
|
|
293
|
+
error: err instanceof Error ? err.message : String(err),
|
|
294
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
295
|
+
requestMeta: buildResponsesRequestMeta(requestArgs),
|
|
296
|
+
});
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function buildResponsesRequestMeta(requestArgs) {
|
|
302
|
+
const meta = {};
|
|
303
|
+
if (Array.isArray(requestArgs.input))
|
|
304
|
+
meta.messagesCount = requestArgs.input.length;
|
|
305
|
+
else if (typeof requestArgs.input === "string")
|
|
306
|
+
meta.messagesCount = 1;
|
|
307
|
+
if (typeof requestArgs.temperature === "number")
|
|
308
|
+
meta.temperature = requestArgs.temperature;
|
|
309
|
+
if (typeof requestArgs.max_output_tokens === "number")
|
|
310
|
+
meta.maxTokens = requestArgs.max_output_tokens;
|
|
311
|
+
return meta;
|
|
312
|
+
}
|
|
313
|
+
function isAnthropicLike(client) {
|
|
314
|
+
const c = client;
|
|
315
|
+
return typeof c?.messages?.create === "function";
|
|
316
|
+
}
|
|
317
|
+
function wrapAnthropic(client, recorder, config) {
|
|
318
|
+
const c = client;
|
|
319
|
+
const originalCreate = c.messages.create.bind(c.messages);
|
|
320
|
+
c.messages.create = async function (...args) {
|
|
321
|
+
const start = Date.now();
|
|
322
|
+
const requestArgs = args[0] || {};
|
|
323
|
+
const id = generateId();
|
|
324
|
+
const isStream = requestArgs.stream === true;
|
|
325
|
+
try {
|
|
326
|
+
const response = await originalCreate(...args);
|
|
327
|
+
if (isStream) {
|
|
328
|
+
return wrapAnthropicStream(response, recorder, requestArgs, start, id, config);
|
|
329
|
+
}
|
|
330
|
+
const r = response;
|
|
331
|
+
const latencyMs = Date.now() - start;
|
|
332
|
+
const model = r.model || requestArgs.model || "unknown";
|
|
333
|
+
const promptTokens = r.usage?.input_tokens ?? 0;
|
|
334
|
+
const completionTokens = r.usage?.output_tokens ?? 0;
|
|
335
|
+
recorder.record({
|
|
336
|
+
id,
|
|
337
|
+
provider: "anthropic",
|
|
338
|
+
model,
|
|
339
|
+
promptTokens,
|
|
340
|
+
completionTokens,
|
|
341
|
+
totalTokens: promptTokens + completionTokens,
|
|
342
|
+
costUsd: calculateCost("anthropic", model, promptTokens, completionTokens),
|
|
343
|
+
latencyMs,
|
|
344
|
+
timestamp: new Date().toISOString(),
|
|
345
|
+
tags: { ...(config.tags ?? {}) },
|
|
346
|
+
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
347
|
+
});
|
|
348
|
+
return response;
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
const errorDetails = extractErrorDetails(err);
|
|
352
|
+
recorder.record({
|
|
353
|
+
id,
|
|
354
|
+
provider: "anthropic",
|
|
355
|
+
model: requestArgs.model || "unknown",
|
|
356
|
+
promptTokens: 0,
|
|
357
|
+
completionTokens: 0,
|
|
358
|
+
totalTokens: 0,
|
|
359
|
+
costUsd: 0,
|
|
360
|
+
latencyMs: Date.now() - start,
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
tags: { ...(config.tags ?? {}) },
|
|
363
|
+
error: err instanceof Error ? err.message : String(err),
|
|
364
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
365
|
+
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
366
|
+
});
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async function* wrapAnthropicStream(stream, recorder, requestArgs, start, id, config) {
|
|
372
|
+
let model = requestArgs.model || "unknown";
|
|
373
|
+
let promptTokens = 0;
|
|
374
|
+
let completionTokens = 0;
|
|
375
|
+
try {
|
|
376
|
+
for await (const event of stream) {
|
|
377
|
+
if (event.type === "message_start" && event.message) {
|
|
378
|
+
if (event.message.model)
|
|
379
|
+
model = event.message.model;
|
|
380
|
+
if (event.message.usage) {
|
|
381
|
+
promptTokens = event.message.usage.input_tokens ?? promptTokens;
|
|
382
|
+
completionTokens = event.message.usage.output_tokens ?? completionTokens;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (event.type === "message_delta" && event.usage) {
|
|
386
|
+
completionTokens = event.usage.output_tokens ?? completionTokens;
|
|
387
|
+
}
|
|
388
|
+
yield event;
|
|
389
|
+
}
|
|
390
|
+
recorder.record({
|
|
391
|
+
id,
|
|
392
|
+
provider: "anthropic",
|
|
393
|
+
model,
|
|
394
|
+
promptTokens,
|
|
395
|
+
completionTokens,
|
|
396
|
+
totalTokens: promptTokens + completionTokens,
|
|
397
|
+
costUsd: calculateCost("anthropic", model, promptTokens, completionTokens),
|
|
398
|
+
latencyMs: Date.now() - start,
|
|
399
|
+
timestamp: new Date().toISOString(),
|
|
400
|
+
tags: { ...(config.tags ?? {}) },
|
|
401
|
+
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
const errorDetails = extractErrorDetails(err);
|
|
406
|
+
recorder.record({
|
|
407
|
+
id,
|
|
408
|
+
provider: "anthropic",
|
|
409
|
+
model,
|
|
410
|
+
promptTokens,
|
|
411
|
+
completionTokens,
|
|
412
|
+
totalTokens: promptTokens + completionTokens,
|
|
413
|
+
costUsd: calculateCost("anthropic", model, promptTokens, completionTokens),
|
|
414
|
+
latencyMs: Date.now() - start,
|
|
415
|
+
timestamp: new Date().toISOString(),
|
|
416
|
+
tags: { ...(config.tags ?? {}) },
|
|
417
|
+
error: err instanceof Error ? err.message : String(err),
|
|
418
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
419
|
+
requestMeta: buildAnthropicRequestMeta(requestArgs),
|
|
420
|
+
});
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function buildAnthropicRequestMeta(requestArgs) {
|
|
425
|
+
const meta = {};
|
|
426
|
+
if (Array.isArray(requestArgs.messages))
|
|
427
|
+
meta.messagesCount = requestArgs.messages.length;
|
|
428
|
+
if (typeof requestArgs.temperature === "number")
|
|
429
|
+
meta.temperature = requestArgs.temperature;
|
|
430
|
+
if (typeof requestArgs.max_tokens === "number")
|
|
431
|
+
meta.maxTokens = requestArgs.max_tokens;
|
|
432
|
+
return meta;
|
|
433
|
+
}
|
|
434
|
+
function isMistralLike(client) {
|
|
435
|
+
const c = client;
|
|
436
|
+
return typeof c?.chat?.complete === "function";
|
|
437
|
+
}
|
|
438
|
+
function wrapMistral(client, recorder, config) {
|
|
439
|
+
const c = client;
|
|
440
|
+
const originalComplete = c.chat.complete.bind(c.chat);
|
|
441
|
+
c.chat.complete = async function (...args) {
|
|
442
|
+
const start = Date.now();
|
|
443
|
+
const requestArgs = args[0] || {};
|
|
444
|
+
const id = generateId();
|
|
445
|
+
try {
|
|
446
|
+
const response = (await originalComplete(...args));
|
|
447
|
+
const latencyMs = Date.now() - start;
|
|
448
|
+
const model = response.model || requestArgs.model || "unknown";
|
|
449
|
+
const promptTokens = response.usage?.prompt_tokens ?? 0;
|
|
450
|
+
const completionTokens = response.usage?.completion_tokens ?? 0;
|
|
451
|
+
recorder.record({
|
|
452
|
+
id,
|
|
453
|
+
provider: "mistral",
|
|
454
|
+
model,
|
|
455
|
+
promptTokens,
|
|
456
|
+
completionTokens,
|
|
457
|
+
totalTokens: response.usage?.total_tokens ?? promptTokens + completionTokens,
|
|
458
|
+
costUsd: calculateCost("mistral", model, promptTokens, completionTokens),
|
|
459
|
+
latencyMs,
|
|
460
|
+
timestamp: new Date().toISOString(),
|
|
461
|
+
tags: { ...(config.tags ?? {}) },
|
|
462
|
+
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
463
|
+
});
|
|
464
|
+
return response;
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
const errorDetails = extractErrorDetails(err);
|
|
468
|
+
recorder.record({
|
|
469
|
+
id,
|
|
470
|
+
provider: "mistral",
|
|
471
|
+
model: requestArgs.model || "unknown",
|
|
472
|
+
promptTokens: 0,
|
|
473
|
+
completionTokens: 0,
|
|
474
|
+
totalTokens: 0,
|
|
475
|
+
costUsd: 0,
|
|
476
|
+
latencyMs: Date.now() - start,
|
|
477
|
+
timestamp: new Date().toISOString(),
|
|
478
|
+
tags: { ...(config.tags ?? {}) },
|
|
479
|
+
error: err instanceof Error ? err.message : String(err),
|
|
480
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
481
|
+
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
482
|
+
});
|
|
483
|
+
throw err;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
if (typeof c.chat.stream === "function") {
|
|
487
|
+
const originalStream = c.chat.stream.bind(c.chat);
|
|
488
|
+
c.chat.stream = async function (...args) {
|
|
489
|
+
const start = Date.now();
|
|
490
|
+
const requestArgs = args[0] || {};
|
|
491
|
+
const id = generateId();
|
|
492
|
+
try {
|
|
493
|
+
const stream = (await originalStream(...args));
|
|
494
|
+
return wrapMistralStream(stream, recorder, requestArgs, start, id, config);
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
// H-2: stream initialization error (= auth/validation/connection)
|
|
498
|
+
const errorDetails = extractErrorDetails(err);
|
|
499
|
+
recorder.record({
|
|
500
|
+
id,
|
|
501
|
+
provider: "mistral",
|
|
502
|
+
model: requestArgs.model || "unknown",
|
|
503
|
+
promptTokens: 0,
|
|
504
|
+
completionTokens: 0,
|
|
505
|
+
totalTokens: 0,
|
|
506
|
+
costUsd: 0,
|
|
507
|
+
latencyMs: Date.now() - start,
|
|
508
|
+
timestamp: new Date().toISOString(),
|
|
509
|
+
tags: { ...(config.tags ?? {}) },
|
|
510
|
+
error: err instanceof Error ? err.message : String(err),
|
|
511
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
512
|
+
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
513
|
+
});
|
|
514
|
+
throw err;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function* wrapMistralStream(stream, recorder, requestArgs, start, id, config) {
|
|
520
|
+
let model = requestArgs.model || "unknown";
|
|
521
|
+
let promptTokens = 0;
|
|
522
|
+
let completionTokens = 0;
|
|
523
|
+
let reportedTotal;
|
|
524
|
+
try {
|
|
525
|
+
for await (const chunk of stream) {
|
|
526
|
+
const inner = chunk.data ?? chunk;
|
|
527
|
+
if (inner.model)
|
|
528
|
+
model = inner.model;
|
|
529
|
+
if (inner.usage) {
|
|
530
|
+
promptTokens = inner.usage.prompt_tokens ?? promptTokens;
|
|
531
|
+
completionTokens = inner.usage.completion_tokens ?? completionTokens;
|
|
532
|
+
if (typeof inner.usage.total_tokens === "number") {
|
|
533
|
+
reportedTotal = inner.usage.total_tokens;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
yield chunk;
|
|
537
|
+
}
|
|
538
|
+
recorder.record({
|
|
539
|
+
id,
|
|
540
|
+
provider: "mistral",
|
|
541
|
+
model,
|
|
542
|
+
promptTokens,
|
|
543
|
+
completionTokens,
|
|
544
|
+
totalTokens: reportedTotal ?? promptTokens + completionTokens,
|
|
545
|
+
costUsd: calculateCost("mistral", model, promptTokens, completionTokens),
|
|
546
|
+
latencyMs: Date.now() - start,
|
|
547
|
+
timestamp: new Date().toISOString(),
|
|
548
|
+
tags: { ...(config.tags ?? {}) },
|
|
549
|
+
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
const errorDetails = extractErrorDetails(err);
|
|
554
|
+
recorder.record({
|
|
555
|
+
id,
|
|
556
|
+
provider: "mistral",
|
|
557
|
+
model,
|
|
558
|
+
promptTokens,
|
|
559
|
+
completionTokens,
|
|
560
|
+
totalTokens: reportedTotal ?? promptTokens + completionTokens,
|
|
561
|
+
costUsd: calculateCost("mistral", model, promptTokens, completionTokens),
|
|
562
|
+
latencyMs: Date.now() - start,
|
|
563
|
+
timestamp: new Date().toISOString(),
|
|
564
|
+
tags: { ...(config.tags ?? {}) },
|
|
565
|
+
error: err instanceof Error ? err.message : String(err),
|
|
566
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
567
|
+
requestMeta: buildMistralRequestMeta(requestArgs),
|
|
568
|
+
});
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function buildMistralRequestMeta(requestArgs) {
|
|
573
|
+
const meta = {};
|
|
574
|
+
if (Array.isArray(requestArgs.messages))
|
|
575
|
+
meta.messagesCount = requestArgs.messages.length;
|
|
576
|
+
if (typeof requestArgs.temperature === "number")
|
|
577
|
+
meta.temperature = requestArgs.temperature;
|
|
578
|
+
if (typeof requestArgs.max_tokens === "number")
|
|
579
|
+
meta.maxTokens = requestArgs.max_tokens;
|
|
580
|
+
return meta;
|
|
581
|
+
}
|
|
582
|
+
function isGeminiLegacyLike(client) {
|
|
583
|
+
const c = client;
|
|
584
|
+
return typeof c?.getGenerativeModel === "function";
|
|
585
|
+
}
|
|
586
|
+
function wrapGeminiLegacy(client, recorder, config) {
|
|
587
|
+
const c = client;
|
|
588
|
+
const originalGetModel = c.getGenerativeModel.bind(c);
|
|
589
|
+
c.getGenerativeModel = function (params) {
|
|
590
|
+
const modelInstance = originalGetModel(params);
|
|
591
|
+
// Idempotent re-wrap: track model instances via WeakSet to avoid double-wrap.
|
|
592
|
+
if (wrappedGeminiModels.has(modelInstance)) {
|
|
593
|
+
return modelInstance;
|
|
594
|
+
}
|
|
595
|
+
wrappedGeminiModels.add(modelInstance);
|
|
596
|
+
wrapGeminiLegacyModel(modelInstance, params.model, recorder, config);
|
|
597
|
+
return modelInstance;
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function wrapGeminiLegacyModel(model, modelName, recorder, config) {
|
|
601
|
+
const originalGenerate = model.generateContent.bind(model);
|
|
602
|
+
model.generateContent = async function (...args) {
|
|
603
|
+
const start = Date.now();
|
|
604
|
+
const id = generateId();
|
|
605
|
+
const requestArgs = args[0];
|
|
606
|
+
try {
|
|
607
|
+
const result = (await originalGenerate(...args));
|
|
608
|
+
const usage = result.response?.usageMetadata;
|
|
609
|
+
const promptTokens = usage?.promptTokenCount ?? 0;
|
|
610
|
+
const completionTokens = usage?.candidatesTokenCount ?? 0;
|
|
611
|
+
recorder.record({
|
|
612
|
+
id,
|
|
613
|
+
provider: "gemini",
|
|
614
|
+
model: modelName,
|
|
615
|
+
promptTokens,
|
|
616
|
+
completionTokens,
|
|
617
|
+
totalTokens: usage?.totalTokenCount ?? promptTokens + completionTokens,
|
|
618
|
+
costUsd: calculateCost("gemini", modelName, promptTokens, completionTokens),
|
|
619
|
+
latencyMs: Date.now() - start,
|
|
620
|
+
timestamp: new Date().toISOString(),
|
|
621
|
+
tags: { ...(config.tags ?? {}) },
|
|
622
|
+
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
623
|
+
});
|
|
624
|
+
return result;
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
const errorDetails = extractErrorDetails(err);
|
|
628
|
+
recorder.record({
|
|
629
|
+
id,
|
|
630
|
+
provider: "gemini",
|
|
631
|
+
model: modelName,
|
|
632
|
+
promptTokens: 0,
|
|
633
|
+
completionTokens: 0,
|
|
634
|
+
totalTokens: 0,
|
|
635
|
+
costUsd: 0,
|
|
636
|
+
latencyMs: Date.now() - start,
|
|
637
|
+
timestamp: new Date().toISOString(),
|
|
638
|
+
tags: { ...(config.tags ?? {}) },
|
|
639
|
+
error: err instanceof Error ? err.message : String(err),
|
|
640
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
641
|
+
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
642
|
+
});
|
|
643
|
+
throw err;
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
if (typeof model.generateContentStream === "function") {
|
|
647
|
+
const originalStream = model.generateContentStream.bind(model);
|
|
648
|
+
model.generateContentStream = async function (...args) {
|
|
649
|
+
const start = Date.now();
|
|
650
|
+
const id = generateId();
|
|
651
|
+
const requestArgs = args[0];
|
|
652
|
+
try {
|
|
653
|
+
const result = (await originalStream(...args));
|
|
654
|
+
if (!result.stream)
|
|
655
|
+
return result;
|
|
656
|
+
// C-2 fix: wrap stream with our async iterator + record in finally
|
|
657
|
+
const originalStreamRef = result.stream;
|
|
658
|
+
const wrappedStream = (async function* () {
|
|
659
|
+
try {
|
|
660
|
+
for await (const chunk of originalStreamRef) {
|
|
661
|
+
yield chunk;
|
|
662
|
+
}
|
|
663
|
+
const finalResponse = await result.response;
|
|
664
|
+
const usage = finalResponse?.usageMetadata;
|
|
665
|
+
const promptTokens = usage?.promptTokenCount ?? 0;
|
|
666
|
+
const completionTokens = usage?.candidatesTokenCount ?? 0;
|
|
667
|
+
recorder.record({
|
|
668
|
+
id,
|
|
669
|
+
provider: "gemini",
|
|
670
|
+
model: modelName,
|
|
671
|
+
promptTokens,
|
|
672
|
+
completionTokens,
|
|
673
|
+
totalTokens: usage?.totalTokenCount ?? promptTokens + completionTokens,
|
|
674
|
+
costUsd: calculateCost("gemini", modelName, promptTokens, completionTokens),
|
|
675
|
+
latencyMs: Date.now() - start,
|
|
676
|
+
timestamp: new Date().toISOString(),
|
|
677
|
+
tags: { ...(config.tags ?? {}) },
|
|
678
|
+
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
const errorDetails = extractErrorDetails(err);
|
|
683
|
+
recorder.record({
|
|
684
|
+
id,
|
|
685
|
+
provider: "gemini",
|
|
686
|
+
model: modelName,
|
|
687
|
+
promptTokens: 0,
|
|
688
|
+
completionTokens: 0,
|
|
689
|
+
totalTokens: 0,
|
|
690
|
+
costUsd: 0,
|
|
691
|
+
latencyMs: Date.now() - start,
|
|
692
|
+
timestamp: new Date().toISOString(),
|
|
693
|
+
tags: { ...(config.tags ?? {}) },
|
|
694
|
+
error: err instanceof Error ? err.message : String(err),
|
|
695
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
696
|
+
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
697
|
+
});
|
|
698
|
+
throw err;
|
|
699
|
+
}
|
|
700
|
+
})();
|
|
701
|
+
return { ...result, stream: wrappedStream };
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
const errorDetails = extractErrorDetails(err);
|
|
705
|
+
recorder.record({
|
|
706
|
+
id,
|
|
707
|
+
provider: "gemini",
|
|
708
|
+
model: modelName,
|
|
709
|
+
promptTokens: 0,
|
|
710
|
+
completionTokens: 0,
|
|
711
|
+
totalTokens: 0,
|
|
712
|
+
costUsd: 0,
|
|
713
|
+
latencyMs: Date.now() - start,
|
|
714
|
+
timestamp: new Date().toISOString(),
|
|
715
|
+
tags: { ...(config.tags ?? {}) },
|
|
716
|
+
error: err instanceof Error ? err.message : String(err),
|
|
717
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
718
|
+
requestMeta: buildGeminiRequestMeta(requestArgs),
|
|
719
|
+
});
|
|
720
|
+
throw err;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function isGeminiNewLike(client) {
|
|
726
|
+
const c = client;
|
|
727
|
+
return typeof c?.models?.generateContent === "function";
|
|
728
|
+
}
|
|
729
|
+
function wrapGeminiNew(client, recorder, config) {
|
|
730
|
+
const c = client;
|
|
731
|
+
const originalGenerate = c.models.generateContent.bind(c.models);
|
|
732
|
+
c.models.generateContent = async function (...args) {
|
|
733
|
+
const start = Date.now();
|
|
734
|
+
const id = generateId();
|
|
735
|
+
const requestArgs = args[0] || {};
|
|
736
|
+
const modelName = requestArgs.model || "unknown";
|
|
737
|
+
try {
|
|
738
|
+
const result = (await originalGenerate(...args));
|
|
739
|
+
const usage = result.usageMetadata;
|
|
740
|
+
const promptTokens = usage?.promptTokenCount ?? 0;
|
|
741
|
+
const completionTokens = usage?.candidatesTokenCount ?? 0;
|
|
742
|
+
recorder.record({
|
|
743
|
+
id,
|
|
744
|
+
provider: "gemini",
|
|
745
|
+
model: modelName,
|
|
746
|
+
promptTokens,
|
|
747
|
+
completionTokens,
|
|
748
|
+
totalTokens: usage?.totalTokenCount ?? promptTokens + completionTokens,
|
|
749
|
+
costUsd: calculateCost("gemini", modelName, promptTokens, completionTokens),
|
|
750
|
+
latencyMs: Date.now() - start,
|
|
751
|
+
timestamp: new Date().toISOString(),
|
|
752
|
+
tags: { ...(config.tags ?? {}) },
|
|
753
|
+
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
754
|
+
});
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
catch (err) {
|
|
758
|
+
const errorDetails = extractErrorDetails(err);
|
|
759
|
+
recorder.record({
|
|
760
|
+
id,
|
|
761
|
+
provider: "gemini",
|
|
762
|
+
model: modelName,
|
|
763
|
+
promptTokens: 0,
|
|
764
|
+
completionTokens: 0,
|
|
765
|
+
totalTokens: 0,
|
|
766
|
+
costUsd: 0,
|
|
767
|
+
latencyMs: Date.now() - start,
|
|
768
|
+
timestamp: new Date().toISOString(),
|
|
769
|
+
tags: { ...(config.tags ?? {}) },
|
|
770
|
+
error: err instanceof Error ? err.message : String(err),
|
|
771
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
772
|
+
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
773
|
+
});
|
|
774
|
+
throw err;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
if (typeof c.models.generateContentStream === "function") {
|
|
778
|
+
const originalStream = c.models.generateContentStream.bind(c.models);
|
|
779
|
+
c.models.generateContentStream = async function (...args) {
|
|
780
|
+
const start = Date.now();
|
|
781
|
+
const id = generateId();
|
|
782
|
+
const requestArgs = args[0] || {};
|
|
783
|
+
const modelName = requestArgs.model || "unknown";
|
|
784
|
+
try {
|
|
785
|
+
const stream = (await originalStream(...args));
|
|
786
|
+
// C-2 + H-3 fix: AsyncGenerator wrap with finally-record + error tied to consumption
|
|
787
|
+
return (async function* () {
|
|
788
|
+
let lastUsage;
|
|
789
|
+
try {
|
|
790
|
+
for await (const chunk of stream) {
|
|
791
|
+
if (chunk.usageMetadata)
|
|
792
|
+
lastUsage = chunk.usageMetadata;
|
|
793
|
+
yield chunk;
|
|
794
|
+
}
|
|
795
|
+
const promptTokens = lastUsage?.promptTokenCount ?? 0;
|
|
796
|
+
const completionTokens = lastUsage?.candidatesTokenCount ?? 0;
|
|
797
|
+
recorder.record({
|
|
798
|
+
id,
|
|
799
|
+
provider: "gemini",
|
|
800
|
+
model: modelName,
|
|
801
|
+
promptTokens,
|
|
802
|
+
completionTokens,
|
|
803
|
+
totalTokens: lastUsage?.totalTokenCount ?? promptTokens + completionTokens,
|
|
804
|
+
costUsd: calculateCost("gemini", modelName, promptTokens, completionTokens),
|
|
805
|
+
latencyMs: Date.now() - start,
|
|
806
|
+
timestamp: new Date().toISOString(),
|
|
807
|
+
tags: { ...(config.tags ?? {}) },
|
|
808
|
+
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
catch (err) {
|
|
812
|
+
const errorDetails = extractErrorDetails(err);
|
|
813
|
+
recorder.record({
|
|
814
|
+
id,
|
|
815
|
+
provider: "gemini",
|
|
816
|
+
model: modelName,
|
|
817
|
+
promptTokens: lastUsage?.promptTokenCount ?? 0,
|
|
818
|
+
completionTokens: lastUsage?.candidatesTokenCount ?? 0,
|
|
819
|
+
totalTokens: lastUsage?.totalTokenCount ??
|
|
820
|
+
(lastUsage?.promptTokenCount ?? 0) +
|
|
821
|
+
(lastUsage?.candidatesTokenCount ?? 0),
|
|
822
|
+
costUsd: 0,
|
|
823
|
+
latencyMs: Date.now() - start,
|
|
824
|
+
timestamp: new Date().toISOString(),
|
|
825
|
+
tags: { ...(config.tags ?? {}) },
|
|
826
|
+
error: err instanceof Error ? err.message : String(err),
|
|
827
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
828
|
+
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
829
|
+
});
|
|
830
|
+
throw err;
|
|
831
|
+
}
|
|
832
|
+
})();
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
const errorDetails = extractErrorDetails(err);
|
|
836
|
+
recorder.record({
|
|
837
|
+
id,
|
|
838
|
+
provider: "gemini",
|
|
839
|
+
model: modelName,
|
|
840
|
+
promptTokens: 0,
|
|
841
|
+
completionTokens: 0,
|
|
842
|
+
totalTokens: 0,
|
|
843
|
+
costUsd: 0,
|
|
844
|
+
latencyMs: Date.now() - start,
|
|
845
|
+
timestamp: new Date().toISOString(),
|
|
846
|
+
tags: { ...(config.tags ?? {}) },
|
|
847
|
+
error: err instanceof Error ? err.message : String(err),
|
|
848
|
+
...(errorDetails ? { errorDetails } : {}),
|
|
849
|
+
requestMeta: buildGeminiNewRequestMeta(requestArgs),
|
|
850
|
+
});
|
|
851
|
+
throw err;
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
function buildGeminiRequestMeta(requestArgs) {
|
|
857
|
+
const meta = {};
|
|
858
|
+
if (typeof requestArgs === "string")
|
|
859
|
+
meta.messagesCount = 1;
|
|
860
|
+
else if (Array.isArray(requestArgs))
|
|
861
|
+
meta.messagesCount = requestArgs.length;
|
|
862
|
+
else if (requestArgs &&
|
|
863
|
+
typeof requestArgs === "object" &&
|
|
864
|
+
"contents" in requestArgs &&
|
|
865
|
+
Array.isArray(requestArgs.contents)) {
|
|
866
|
+
meta.messagesCount = requestArgs.contents.length;
|
|
867
|
+
}
|
|
868
|
+
return meta;
|
|
869
|
+
}
|
|
870
|
+
function buildGeminiNewRequestMeta(requestArgs) {
|
|
871
|
+
const meta = {};
|
|
872
|
+
if (Array.isArray(requestArgs.contents)) {
|
|
873
|
+
meta.messagesCount = requestArgs.contents.length;
|
|
874
|
+
}
|
|
875
|
+
else if (typeof requestArgs.contents === "string") {
|
|
876
|
+
meta.messagesCount = 1;
|
|
877
|
+
}
|
|
878
|
+
return meta;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Lightweight ID generator (not strictly ULID-compatible). Will be swapped for
|
|
882
|
+
* the `ulid` library in a later release.
|
|
883
|
+
*/
|
|
884
|
+
function generateId() {
|
|
885
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
886
|
+
}
|
|
887
|
+
//# sourceMappingURL=client.js.map
|