@ax-llm/ax 10.0.43 → 10.0.45

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/index.js CHANGED
@@ -76,22 +76,29 @@ var AxSpanKindValues = /* @__PURE__ */ ((AxSpanKindValues2) => {
76
76
  // util/apicall.ts
77
77
  import path from "path";
78
78
  import {
79
+ ReadableStream,
79
80
  TextDecoderStream as TextDecoderStreamNative
80
81
  } from "stream/web";
81
82
  import "@opentelemetry/api";
82
83
 
83
84
  // util/sse.ts
84
- import { TransformStream } from "stream/web";
85
- var SSEParser = class extends TransformStream {
86
- constructor(dataParser = JSON.parse) {
85
+ import { TransformStream as TransformStream2 } from "stream/web";
86
+ var SSEParser = class extends TransformStream2 {
87
+ buffer = "";
88
+ currentEvent = { rawData: "" };
89
+ dataParser;
90
+ onError;
91
+ constructor(options = {}) {
87
92
  super({
88
93
  transform: (chunk, controller) => this.handleChunk(chunk, controller),
89
94
  flush: (controller) => this.handleFlush(controller)
90
95
  });
91
- this.dataParser = dataParser;
96
+ this.dataParser = options.dataParser || JSON.parse;
97
+ this.onError = options.onError || ((error, rawData) => {
98
+ console.warn("Failed to parse event data:", error);
99
+ console.log("Raw data that failed to parse:", rawData);
100
+ });
92
101
  }
93
- buffer = "";
94
- currentEvent = { rawData: "" };
95
102
  handleChunk(chunk, controller) {
96
103
  this.buffer += chunk;
97
104
  this.processBuffer(controller);
@@ -99,24 +106,28 @@ var SSEParser = class extends TransformStream {
99
106
  handleFlush(controller) {
100
107
  this.processBuffer(controller);
101
108
  if (this.currentEvent.rawData) {
102
- this.emitEvent(controller);
109
+ this.processEvent(controller);
103
110
  }
104
111
  }
105
112
  processBuffer(controller) {
106
- const lines = this.buffer.split(/\r\n|\r|\n/);
113
+ const normalizedBuffer = this.buffer.replace(/\r\n|\r/g, "\n");
114
+ const lines = normalizedBuffer.split("\n");
107
115
  this.buffer = lines.pop() || "";
108
116
  for (const line of lines) {
109
- if (line.trim() === "") {
110
- this.emitEvent(controller);
117
+ if (line === "") {
118
+ this.processEvent(controller);
111
119
  } else {
112
120
  this.parseLine(line);
113
121
  }
114
122
  }
115
123
  }
116
124
  parseLine(line) {
125
+ if (line.startsWith(":")) {
126
+ return;
127
+ }
117
128
  const colonIndex = line.indexOf(":");
118
129
  if (colonIndex === -1) {
119
- this.currentEvent.rawData += this.currentEvent.rawData ? "\n" + line.trim() : line.trim();
130
+ this.currentEvent.rawData += (this.currentEvent.rawData && !this.currentEvent.rawData.endsWith("\n") ? "\n" : "") + line.trim();
120
131
  return;
121
132
  }
122
133
  const field = line.slice(0, colonIndex).trim();
@@ -126,7 +137,7 @@ var SSEParser = class extends TransformStream {
126
137
  this.currentEvent.event = value;
127
138
  break;
128
139
  case "data":
129
- this.currentEvent.rawData += this.currentEvent.rawData ? "\n" + value : value;
140
+ this.currentEvent.rawData += (this.currentEvent.rawData && !this.currentEvent.rawData.endsWith("\n") ? "\n" : "") + value;
130
141
  break;
131
142
  case "id":
132
143
  this.currentEvent.id = value;
@@ -140,21 +151,20 @@ var SSEParser = class extends TransformStream {
140
151
  }
141
152
  }
142
153
  }
143
- emitEvent(controller) {
154
+ processEvent(controller) {
144
155
  if (this.currentEvent.rawData) {
145
- if (this.currentEvent.rawData.trim() === "[DONE]" || this.currentEvent.rawData.trim().startsWith("[")) {
156
+ if (!this.currentEvent.event) {
157
+ this.currentEvent.event = "message";
158
+ }
159
+ if (this.currentEvent.rawData.trim() === "[DONE]") {
160
+ this.currentEvent = { rawData: "" };
146
161
  return;
147
- } else {
148
- try {
149
- const parsedData = this.dataParser(this.currentEvent.rawData);
150
- controller.enqueue(parsedData);
151
- } catch (e) {
152
- console.warn("Failed to parse event data:", e);
153
- console.log(
154
- "Raw data that failed to parse:",
155
- this.currentEvent.rawData
156
- );
157
- }
162
+ }
163
+ try {
164
+ const parsedData = this.dataParser(this.currentEvent.rawData);
165
+ controller.enqueue(parsedData);
166
+ } catch (e) {
167
+ this.onError(e, this.currentEvent.rawData);
158
168
  }
159
169
  this.currentEvent = { rawData: "" };
160
170
  }
@@ -163,7 +173,7 @@ var SSEParser = class extends TransformStream {
163
173
 
164
174
  // util/stream.ts
165
175
  import {
166
- TransformStream as TransformStream2
176
+ TransformStream as TransformStream3
167
177
  } from "stream/web";
168
178
  var TextDecodeTransformer = class {
169
179
  decoder;
@@ -186,59 +196,312 @@ var TextDecodeTransformer = class {
186
196
  }
187
197
  }
188
198
  };
189
- var TextDecoderStreamPolyfill = class extends TransformStream2 {
199
+ var TextDecoderStreamPolyfill = class extends TransformStream3 {
190
200
  constructor() {
191
201
  super(new TextDecodeTransformer());
192
202
  }
193
203
  };
194
204
 
195
205
  // util/apicall.ts
206
+ var defaultRetryConfig = {
207
+ maxRetries: 3,
208
+ initialDelayMs: 1e3,
209
+ maxDelayMs: 6e4,
210
+ // Increased to 60 seconds
211
+ backoffFactor: 2,
212
+ retryableStatusCodes: [500, 408, 429, 502, 503, 504]
213
+ };
214
+ var defaultTimeoutMs = 3e4;
196
215
  var textDecoderStream = TextDecoderStreamNative ?? TextDecoderStreamPolyfill;
216
+ var AxAIServiceError = class extends Error {
217
+ constructor(message, url, requestBody, context = {}) {
218
+ super(message);
219
+ this.url = url;
220
+ this.requestBody = requestBody;
221
+ this.context = context;
222
+ this.name = "AxAIServiceError";
223
+ this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
224
+ this.errorId = crypto.randomUUID();
225
+ }
226
+ timestamp;
227
+ errorId;
228
+ toString() {
229
+ return `${this.name} [${this.errorId}]: ${this.message}
230
+ Timestamp: ${this.timestamp}
231
+ URL: ${this.url}${this.requestBody ? `
232
+ Request Body: ${JSON.stringify(this.requestBody, null, 2)}` : ""}${this.context ? `
233
+ Context: ${JSON.stringify(this.context, null, 2)}` : ""}`;
234
+ }
235
+ toJSON() {
236
+ return {
237
+ name: this.name,
238
+ errorId: this.errorId,
239
+ message: this.message,
240
+ timestamp: this.timestamp,
241
+ url: this.url,
242
+ requestBody: this.requestBody,
243
+ context: this.context,
244
+ stack: this.stack
245
+ };
246
+ }
247
+ };
248
+ var AxAIServiceStatusError = class extends AxAIServiceError {
249
+ constructor(status, statusText, url, requestBody, context) {
250
+ super(`HTTP ${status} - ${statusText}`, url, requestBody, {
251
+ httpStatus: status,
252
+ httpStatusText: statusText,
253
+ ...context
254
+ });
255
+ this.status = status;
256
+ this.statusText = statusText;
257
+ this.name = "AxAIServiceStatusError";
258
+ }
259
+ };
260
+ var AxAIServiceNetworkError = class extends AxAIServiceError {
261
+ constructor(originalError, url, requestBody, context) {
262
+ super(`Network Error: ${originalError.message}`, url, requestBody, {
263
+ originalErrorName: originalError.name,
264
+ originalErrorStack: originalError.stack,
265
+ ...context
266
+ });
267
+ this.originalError = originalError;
268
+ this.name = "AxAIServiceNetworkError";
269
+ this.stack = originalError.stack;
270
+ }
271
+ };
272
+ var AxAIServiceResponseError = class extends AxAIServiceError {
273
+ constructor(message, url, requestBody, context) {
274
+ super(message, url, requestBody, context);
275
+ this.name = "AxAIServiceResponseError";
276
+ }
277
+ };
278
+ var AxAIServiceStreamTerminatedError = class extends AxAIServiceError {
279
+ constructor(url, requestBody, lastChunk, context) {
280
+ super("Stream terminated unexpectedly by remote host", url, requestBody, {
281
+ lastChunk,
282
+ ...context
283
+ });
284
+ this.lastChunk = lastChunk;
285
+ this.name = "AxAIServiceStreamTerminatedError";
286
+ }
287
+ };
288
+ var AxAIServiceTimeoutError = class extends AxAIServiceError {
289
+ constructor(url, timeoutMs, requestBody, context) {
290
+ super(`Request timeout after ${timeoutMs}ms`, url, requestBody, {
291
+ timeoutMs,
292
+ ...context
293
+ });
294
+ this.name = "AxAIServiceTimeoutError";
295
+ }
296
+ };
297
+ var AxAIServiceAuthenticationError = class extends AxAIServiceError {
298
+ constructor(url, requestBody, context) {
299
+ super("Authentication failed", url, requestBody, context);
300
+ this.name = "AxAIServiceAuthenticationError";
301
+ }
302
+ };
303
+ function calculateRetryDelay(attempt, config) {
304
+ const delay = Math.min(
305
+ config.maxDelayMs,
306
+ config.initialDelayMs * Math.pow(config.backoffFactor, attempt)
307
+ );
308
+ return delay * (0.75 + Math.random() * 0.5);
309
+ }
310
+ function createRequestMetrics() {
311
+ return {
312
+ startTime: Date.now(),
313
+ retryCount: 0
314
+ };
315
+ }
316
+ function updateRetryMetrics(metrics) {
317
+ metrics.retryCount++;
318
+ metrics.lastRetryTime = Date.now();
319
+ }
197
320
  var apiCall = async (api, json) => {
321
+ const retryConfig = { ...defaultRetryConfig, ...api.retry };
322
+ const timeoutMs = api.timeout ?? defaultTimeoutMs;
323
+ const metrics = createRequestMetrics();
198
324
  const baseUrl = new URL(process.env["PROXY"] ?? api.url);
199
325
  const apiPath = path.join(baseUrl.pathname, api.name ?? "/", baseUrl.search);
200
326
  const apiUrl = new URL(apiPath, baseUrl);
327
+ const requestId = crypto.randomUUID();
201
328
  if (api.span?.isRecording()) {
202
329
  api.span.setAttributes({
203
330
  "http.request.method": api.put ? "PUT" : "POST",
204
- "url.full": apiUrl.href
331
+ "url.full": apiUrl.href,
332
+ "request.id": requestId,
333
+ "request.startTime": metrics.startTime
205
334
  });
206
335
  }
207
- let res;
208
- try {
209
- res = await (api.fetch ?? fetch)(apiUrl, {
210
- method: api.put ? "PUT" : "POST",
211
- headers: {
212
- "Content-Type": "application/json",
213
- ...api.headers
214
- },
215
- body: JSON.stringify(json)
216
- });
217
- if (res.status >= 400) {
218
- const reqBody = JSON.stringify(json, null, 2);
219
- throw new Error(
220
- `API Request Error: ${res.status}, ${res.statusText}
221
- :Request Body: ${reqBody}`
222
- );
223
- }
224
- if (!api.stream) {
225
- const resJson = await res.json();
226
- return resJson;
227
- }
228
- if (!res.body) {
229
- throw new Error("Response body is null");
230
- }
231
- const st = res.body.pipeThrough(new textDecoderStream()).pipeThrough(new SSEParser());
232
- return st;
233
- } catch (e) {
234
- if (api.span?.isRecording()) {
235
- api.span.recordException(e);
336
+ let attempt = 0;
337
+ while (true) {
338
+ const controller = new AbortController();
339
+ let timeoutId = setTimeout(() => {
340
+ controller.abort("Request timeout");
341
+ }, timeoutMs);
342
+ try {
343
+ const res = await (api.fetch ?? fetch)(apiUrl, {
344
+ method: api.put ? "PUT" : "POST",
345
+ headers: {
346
+ "Content-Type": "application/json",
347
+ "X-Request-ID": requestId,
348
+ "X-Retry-Count": attempt.toString(),
349
+ ...api.headers
350
+ },
351
+ body: JSON.stringify(json),
352
+ signal: controller.signal
353
+ });
354
+ clearTimeout(timeoutId);
355
+ if (res.status === 401 || res.status === 403) {
356
+ throw new AxAIServiceAuthenticationError(apiUrl.href, json, { metrics });
357
+ }
358
+ if (res.status >= 400 && attempt < retryConfig.maxRetries && retryConfig.retryableStatusCodes.includes(res.status)) {
359
+ const delay = calculateRetryDelay(attempt, retryConfig);
360
+ attempt++;
361
+ updateRetryMetrics(metrics);
362
+ if (api.span?.isRecording()) {
363
+ api.span.addEvent("retry", {
364
+ attempt,
365
+ delay,
366
+ status: res.status,
367
+ "metrics.startTime": metrics.startTime,
368
+ "metrics.retryCount": metrics.retryCount,
369
+ "metrics.lastRetryTime": metrics.lastRetryTime
370
+ });
371
+ }
372
+ clearTimeout(timeoutId);
373
+ continue;
374
+ }
375
+ if (res.status >= 400) {
376
+ throw new AxAIServiceStatusError(
377
+ res.status,
378
+ res.statusText,
379
+ apiUrl.href,
380
+ json,
381
+ { metrics }
382
+ );
383
+ }
384
+ if (!api.stream) {
385
+ const resJson = await res.json();
386
+ if (api.span?.isRecording()) {
387
+ api.span.setAttributes({
388
+ "response.time": Date.now() - metrics.startTime,
389
+ "response.retries": metrics.retryCount
390
+ });
391
+ }
392
+ return resJson;
393
+ }
394
+ if (!res.body) {
395
+ throw new AxAIServiceResponseError(
396
+ "Response body is null",
397
+ apiUrl.href,
398
+ json,
399
+ { metrics }
400
+ );
401
+ }
402
+ let lastChunk;
403
+ let chunkCount = 0;
404
+ const trackingStream = new TransformStream({
405
+ transform(chunk, controller2) {
406
+ lastChunk = chunk;
407
+ chunkCount++;
408
+ metrics.streamChunks = chunkCount;
409
+ metrics.lastChunkTime = Date.now();
410
+ controller2.enqueue(chunk);
411
+ },
412
+ flush(controller2) {
413
+ if (api.span?.isRecording()) {
414
+ api.span.setAttributes({
415
+ "stream.chunks": chunkCount,
416
+ "stream.duration": Date.now() - metrics.startTime,
417
+ "response.retries": metrics.retryCount
418
+ });
419
+ }
420
+ controller2.terminate();
421
+ }
422
+ });
423
+ const wrappedStream = new ReadableStream({
424
+ start(controller2) {
425
+ const reader = res.body.pipeThrough(new textDecoderStream()).pipeThrough(new SSEParser()).pipeThrough(trackingStream).getReader();
426
+ async function read() {
427
+ try {
428
+ while (true) {
429
+ const { done, value } = await reader.read();
430
+ if (done) {
431
+ controller2.close();
432
+ break;
433
+ } else {
434
+ controller2.enqueue(value);
435
+ }
436
+ }
437
+ } catch (e) {
438
+ const error = e;
439
+ const streamMetrics = {
440
+ ...metrics,
441
+ streamDuration: Date.now() - metrics.startTime
442
+ };
443
+ if (error.name === "AbortError" || error.message?.includes("aborted")) {
444
+ controller2.error(
445
+ new AxAIServiceStreamTerminatedError(
446
+ apiUrl.href,
447
+ json,
448
+ lastChunk,
449
+ { streamMetrics }
450
+ )
451
+ );
452
+ } else {
453
+ controller2.error(
454
+ new AxAIServiceResponseError(
455
+ `Stream processing error: ${error.message}`,
456
+ apiUrl.href,
457
+ json,
458
+ { streamMetrics }
459
+ )
460
+ );
461
+ }
462
+ } finally {
463
+ reader.releaseLock();
464
+ }
465
+ }
466
+ read();
467
+ }
468
+ });
469
+ return wrappedStream;
470
+ } catch (error) {
471
+ clearTimeout(timeoutId);
472
+ if (error instanceof Error && error.name === "AbortError") {
473
+ throw new AxAIServiceTimeoutError(apiUrl.href, timeoutMs, json, {
474
+ metrics
475
+ });
476
+ }
477
+ if (api.span?.isRecording()) {
478
+ api.span.recordException(error);
479
+ api.span.setAttributes({
480
+ "error.time": Date.now() - metrics.startTime,
481
+ "error.retries": metrics.retryCount
482
+ });
483
+ }
484
+ if (error instanceof AxAIServiceNetworkError && attempt < retryConfig.maxRetries) {
485
+ const delay = calculateRetryDelay(attempt, retryConfig);
486
+ attempt++;
487
+ updateRetryMetrics(metrics);
488
+ if (api.span?.isRecording()) {
489
+ api.span.addEvent("retry", {
490
+ attempt,
491
+ delay,
492
+ error: error.message,
493
+ "metrics.startTime": metrics.startTime,
494
+ "metrics.retryCount": metrics.retryCount,
495
+ "metrics.lastRetryTime": metrics.lastRetryTime
496
+ });
497
+ }
498
+ continue;
499
+ }
500
+ if (error instanceof AxAIServiceError) {
501
+ error.context["metrics"] = metrics;
502
+ }
503
+ throw error;
236
504
  }
237
- const reqBody = JSON.stringify(json, null, 2);
238
- throw new Error(
239
- `API Response Error: ${apiUrl.href}, ${e}
240
- Request Body: ${reqBody}`
241
- );
242
505
  }
243
506
  };
244
507
 
@@ -275,7 +538,7 @@ var ColorLog = class {
275
538
 
276
539
  // util/transform.ts
277
540
  import {
278
- TransformStream as TransformStream3
541
+ TransformStream as TransformStream4
279
542
  } from "stream/web";
280
543
  var TypeTransformer = class {
281
544
  buffer;
@@ -298,7 +561,7 @@ var TypeTransformer = class {
298
561
  controller.terminate();
299
562
  }
300
563
  };
301
- var RespTransformStream = class extends TransformStream3 {
564
+ var RespTransformStream = class extends TransformStream4 {
302
565
  constructor(transformFn, doneCallback) {
303
566
  super(new TypeTransformer(transformFn, doneCallback));
304
567
  }
@@ -814,10 +1077,10 @@ var GoogleVertexAuth = class {
814
1077
  }
815
1078
  const client = await this.getAuthenticatedClient();
816
1079
  const tokenResponse = await client.getAccessToken();
817
- this.currentToken = tokenResponse.token ?? void 0;
1080
+ this.currentToken = tokenResponse.token;
818
1081
  const expiry = this.getExpiry(tokenResponse);
819
- const fiveMinutes = 5 * 60 * 1e3;
820
- this.tokenExpiry = expiry - fiveMinutes;
1082
+ const tenMinutes = 10 * 60 * 1e3;
1083
+ this.tokenExpiry = expiry - tenMinutes;
821
1084
  return this.currentToken;
822
1085
  }
823
1086
  /**
@@ -837,8 +1100,6 @@ var GoogleVertexAuth = class {
837
1100
  } else {
838
1101
  console.warn("Unknown expiry type", responseExpiry);
839
1102
  }
840
- } else {
841
- console.warn("No expiry date found in response", tokenResponse.res?.data);
842
1103
  }
843
1104
  return expiry;
844
1105
  }
@@ -3261,7 +3522,7 @@ var AxAI = class {
3261
3522
  };
3262
3523
 
3263
3524
  // dsp/generate.ts
3264
- import { ReadableStream } from "stream/web";
3525
+ import { ReadableStream as ReadableStream2 } from "stream/web";
3265
3526
  import { SpanKind as SpanKind2 } from "@opentelemetry/api";
3266
3527
 
3267
3528
  // ai/util.ts
@@ -5276,7 +5537,7 @@ var AxGen = class extends AxProgramWithSignature {
5276
5537
  mem,
5277
5538
  options
5278
5539
  });
5279
- if (res instanceof ReadableStream) {
5540
+ if (res instanceof ReadableStream2) {
5280
5541
  yield* this.processStreamingResponse({
5281
5542
  ai,
5282
5543
  model,
@@ -5802,11 +6063,35 @@ var AxBalancer = class _AxBalancer {
5802
6063
  try {
5803
6064
  return await this.currentService.chat(req, options);
5804
6065
  } catch (e) {
5805
- console.warn(`Service ${this.currentService.getName()} failed`);
6066
+ if (!(e instanceof AxAIServiceError)) {
6067
+ throw e;
6068
+ }
6069
+ switch (e.constructor) {
6070
+ case AxAIServiceAuthenticationError:
6071
+ throw e;
6072
+ case AxAIServiceStatusError:
6073
+ break;
6074
+ case AxAIServiceNetworkError:
6075
+ break;
6076
+ case AxAIServiceResponseError:
6077
+ break;
6078
+ case AxAIServiceStreamTerminatedError:
6079
+ break;
6080
+ case AxAIServiceTimeoutError:
6081
+ break;
6082
+ default:
6083
+ throw e;
6084
+ }
6085
+ console.warn(
6086
+ `AxBalancer: Service ${this.currentService.getName()} failed`,
6087
+ e
6088
+ );
5806
6089
  if (!this.getNextService()) {
5807
6090
  throw e;
5808
6091
  }
5809
- console.warn(`Switching to service ${this.currentService.getName()}`);
6092
+ console.warn(
6093
+ `AxBalancer: Switching to service ${this.currentService.getName()}`
6094
+ );
5810
6095
  }
5811
6096
  }
5812
6097
  }
@@ -7487,6 +7772,13 @@ export {
7487
7772
  AxAIOpenAIModel,
7488
7773
  AxAIReka,
7489
7774
  AxAIRekaModel,
7775
+ AxAIServiceAuthenticationError,
7776
+ AxAIServiceError,
7777
+ AxAIServiceNetworkError,
7778
+ AxAIServiceResponseError,
7779
+ AxAIServiceStatusError,
7780
+ AxAIServiceStreamTerminatedError,
7781
+ AxAIServiceTimeoutError,
7490
7782
  AxAITogether,
7491
7783
  AxAgent,
7492
7784
  AxApacheTika,