@jambonz/llm 0.4.6 → 0.5.1

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.
@@ -117,9 +117,22 @@ async function* streamFromOpenAI(client, req, options) {
117
117
  const param = options.tokensParam ?? (needsMaxCompletionTokens(req.model) ? "max_completion_tokens" : "max_tokens");
118
118
  body[param] = effectiveMaxTokens;
119
119
  }
120
+ const t0 = process.hrtime.bigint();
121
+ let t1;
122
+ let t2;
120
123
  let stream;
124
+ let responseHeaders;
121
125
  try {
122
- stream = await client.chat.completions.create(body, { signal: req.signal });
126
+ const apiPromise = client.chat.completions.create(body, { signal: req.signal });
127
+ const maybeWithResponse = apiPromise.withResponse;
128
+ if (typeof maybeWithResponse === "function") {
129
+ const result = await maybeWithResponse.call(apiPromise);
130
+ stream = result.data;
131
+ responseHeaders = result.response?.headers;
132
+ } else {
133
+ stream = await apiPromise;
134
+ }
135
+ t1 = process.hrtime.bigint();
123
136
  } catch (err) {
124
137
  if (isAbortError(err)) {
125
138
  yield { type: "end", finishReason: "aborted" };
@@ -151,6 +164,7 @@ async function* streamFromOpenAI(client, req, options) {
151
164
  const delta = choice.delta;
152
165
  const finishReason = choice.finish_reason;
153
166
  if (delta?.content) {
167
+ if (t2 === void 0) t2 = process.hrtime.bigint();
154
168
  yield { type: "token", text: delta.content };
155
169
  }
156
170
  if (delta?.tool_calls) {
@@ -214,6 +228,20 @@ async function* streamFromOpenAI(client, req, options) {
214
228
  ...usage ? { usage } : {},
215
229
  ...finalRawReason ? { rawReason: finalRawReason } : {}
216
230
  };
231
+ if (options.vendorMetadataExtractor && responseHeaders) {
232
+ const metadata = options.vendorMetadataExtractor(responseHeaders);
233
+ if (metadata && Object.keys(metadata).length > 0) {
234
+ endEvent.vendorMetadata = metadata;
235
+ }
236
+ }
237
+ if (t1 !== void 0) {
238
+ const t3 = process.hrtime.bigint();
239
+ endEvent.clientTiming = {
240
+ requestToHeadersMs: Number(t1 - t0) / 1e6,
241
+ ...t2 !== void 0 ? { headersToFirstTokenMs: Number(t2 - t1) / 1e6 } : {},
242
+ totalMs: Number(t3 - t0) / 1e6
243
+ };
244
+ }
217
245
  yield endEvent;
218
246
  }
219
247
  function appendOpenAIAssistantToolCall(history, toolCalls) {
@@ -341,6 +369,40 @@ function isAbortError(err) {
341
369
  return e.name === "APIUserAbortError" || e.name === "AbortError" || e.code === "ABORT_ERR" || typeof e.message === "string" && /aborted/i.test(e.message);
342
370
  }
343
371
 
372
+ // src/adapters/_metadata.ts
373
+ function makeMetadataExtractor(fields) {
374
+ return (headers) => {
375
+ const out = {};
376
+ for (const f of fields) {
377
+ const raw = headers.get(f.header);
378
+ if (raw === null || raw === void 0 || raw === "") continue;
379
+ if (f.numeric) {
380
+ const n = Number(raw);
381
+ if (Number.isFinite(n)) out[f.key] = n;
382
+ } else {
383
+ out[f.key] = raw;
384
+ }
385
+ }
386
+ return out;
387
+ };
388
+ }
389
+ function extractMetadata(fields, headers) {
390
+ const headersInstance = headers instanceof Headers ? headers : recordToHeaders(headers);
391
+ return makeMetadataExtractor(fields)(headersInstance);
392
+ }
393
+ function recordToHeaders(record) {
394
+ const h = new Headers();
395
+ for (const [name, value] of Object.entries(record)) {
396
+ if (value === void 0) continue;
397
+ if (Array.isArray(value)) {
398
+ for (const v of value) h.append(name, v);
399
+ } else {
400
+ h.set(name, value);
401
+ }
402
+ }
403
+ return h;
404
+ }
405
+
344
406
  // src/adapters/openai/manifest.ts
345
407
  var OPENAI_KNOWN_MODELS = [
346
408
  {
@@ -442,6 +504,12 @@ var openAIManifest = {
442
504
  };
443
505
 
444
506
  // src/adapters/openai/adapter.ts
507
+ var OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
508
+ { header: "x-request-id", key: "request_id" },
509
+ { header: "openai-processing-ms", key: "processing_ms", numeric: true },
510
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
511
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
512
+ ]);
445
513
  var OpenAIAdapter = class {
446
514
  vendor = openAIManifest.vendor;
447
515
  acceptedAuth = ["apiKey"];
@@ -459,7 +527,8 @@ var OpenAIAdapter = class {
459
527
  }
460
528
  stream(req) {
461
529
  return streamFromOpenAI(this.ensureClient(), req, {
462
- knownModels: openAIManifest.knownModels
530
+ knownModels: openAIManifest.knownModels,
531
+ vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR
463
532
  });
464
533
  }
465
534
  appendAssistantToolCall(history, toolCalls) {
@@ -691,6 +760,12 @@ var anthropicManifest = {
691
760
  };
692
761
 
693
762
  // src/adapters/anthropic/adapter.ts
763
+ var ANTHROPIC_METADATA_EXTRACTOR = makeMetadataExtractor([
764
+ { header: "request-id", key: "request_id" },
765
+ { header: "anthropic-ratelimit-requests-remaining", key: "requests_remaining", numeric: true },
766
+ { header: "anthropic-ratelimit-input-tokens-remaining", key: "input_tokens_remaining", numeric: true },
767
+ { header: "anthropic-ratelimit-output-tokens-remaining", key: "output_tokens_remaining", numeric: true }
768
+ ]);
694
769
  var AnthropicAdapter = class {
695
770
  vendor = anthropicManifest.vendor;
696
771
  acceptedAuth = ["apiKey"];
@@ -725,11 +800,20 @@ var AnthropicAdapter = class {
725
800
  if (tools) body.tools = tools;
726
801
  if (req.temperature !== void 0) body.temperature = req.temperature;
727
802
  let stream;
803
+ let responseHeaders;
728
804
  try {
729
- stream = await client.messages.create(
805
+ const apiPromise = client.messages.create(
730
806
  body,
731
807
  { signal: req.signal }
732
808
  );
809
+ const maybeWithResponse = apiPromise.withResponse;
810
+ if (typeof maybeWithResponse === "function") {
811
+ const result = await maybeWithResponse.call(apiPromise);
812
+ stream = result.data;
813
+ responseHeaders = result.response?.headers;
814
+ } else {
815
+ stream = await apiPromise;
816
+ }
733
817
  } catch (err) {
734
818
  if (isAbortError2(err)) {
735
819
  yield { type: "end", finishReason: "aborted" };
@@ -840,6 +924,12 @@ var AnthropicAdapter = class {
840
924
  ...usage ? { usage } : {},
841
925
  ...stopReason ? { rawReason: stopReason } : {}
842
926
  };
927
+ if (responseHeaders) {
928
+ const metadata = ANTHROPIC_METADATA_EXTRACTOR(responseHeaders);
929
+ if (Object.keys(metadata).length > 0) {
930
+ endEvent.vendorMetadata = metadata;
931
+ }
932
+ }
843
933
  yield endEvent;
844
934
  }
845
935
  appendAssistantToolCall(history, toolCalls) {
@@ -1632,6 +1722,9 @@ var vertexOpenAIManifest = {
1632
1722
  };
1633
1723
 
1634
1724
  // src/adapters/vertex-openai/adapter.ts
1725
+ var VERTEX_OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
1726
+ { header: "x-request-id", key: "request_id" }
1727
+ ]);
1635
1728
  var VertexOpenAIAdapter = class {
1636
1729
  vendor = vertexOpenAIManifest.vendor;
1637
1730
  acceptedAuth = ["vertexServiceAccount"];
@@ -1684,7 +1777,8 @@ var VertexOpenAIAdapter = class {
1684
1777
  // endpoint predates `max_completion_tokens` and may not recognize it.
1685
1778
  // The model-id heuristic wouldn't catch a partner model named in the
1686
1779
  // OpenAI style (unlikely today, but defensive).
1687
- tokensParam: "max_tokens"
1780
+ tokensParam: "max_tokens",
1781
+ vendorMetadataExtractor: VERTEX_OPENAI_METADATA_EXTRACTOR
1688
1782
  });
1689
1783
  }
1690
1784
  appendAssistantToolCall(history, toolCalls) {
@@ -2142,6 +2236,21 @@ var BedrockAdapter = class {
2142
2236
  ...usage ? { usage } : {},
2143
2237
  ...stopReason ? { rawReason: stopReason } : {}
2144
2238
  };
2239
+ const httpHeaders = response.$metadata?.httpHeaders;
2240
+ if (httpHeaders) {
2241
+ const metadata = extractMetadata(
2242
+ [
2243
+ { header: "x-amzn-bedrock-invocation-latency", key: "invocation_latency_ms", numeric: true },
2244
+ { header: "x-amzn-bedrock-cache-read-input-token-count", key: "cache_read_input_tokens", numeric: true },
2245
+ { header: "x-amzn-bedrock-cache-write-input-token-count", key: "cache_write_input_tokens", numeric: true },
2246
+ { header: "x-amzn-bedrock-trace-id", key: "trace_id" }
2247
+ ],
2248
+ httpHeaders
2249
+ );
2250
+ if (Object.keys(metadata).length > 0) {
2251
+ endEvent.vendorMetadata = metadata;
2252
+ }
2253
+ }
2145
2254
  yield endEvent;
2146
2255
  }
2147
2256
  appendAssistantToolCall(history, toolCalls) {
@@ -2393,6 +2502,14 @@ var azureOpenAIManifest = {
2393
2502
  };
2394
2503
 
2395
2504
  // src/adapters/azure-openai/adapter.ts
2505
+ var AZURE_OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
2506
+ { header: "x-ms-region", key: "region" },
2507
+ { header: "x-ms-deployment-name", key: "deployment" },
2508
+ { header: "apim-request-id", key: "request_id" },
2509
+ { header: "azureml-model-session", key: "model_session" },
2510
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
2511
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
2512
+ ]);
2396
2513
  var AzureOpenAIAdapter = class {
2397
2514
  vendor = azureOpenAIManifest.vendor;
2398
2515
  acceptedAuth = ["azureOpenAIApiKey"];
@@ -2427,7 +2544,8 @@ var AzureOpenAIAdapter = class {
2427
2544
  stream(req) {
2428
2545
  return streamFromOpenAI(this.ensureClient(), req, {
2429
2546
  knownModels: azureOpenAIManifest.knownModels,
2430
- tokensParam: "max_completion_tokens"
2547
+ tokensParam: "max_completion_tokens",
2548
+ vendorMetadataExtractor: AZURE_OPENAI_METADATA_EXTRACTOR
2431
2549
  });
2432
2550
  }
2433
2551
  appendAssistantToolCall(history, toolCalls) {
@@ -2552,6 +2670,12 @@ var groqManifest = {
2552
2670
  };
2553
2671
 
2554
2672
  // src/adapters/groq/adapter.ts
2673
+ var GROQ_METADATA_EXTRACTOR = makeMetadataExtractor([
2674
+ { header: "x-request-id", key: "request_id" },
2675
+ { header: "x-groq-region", key: "region" },
2676
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
2677
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
2678
+ ]);
2555
2679
  var GroqAdapter = class extends OpenAIAdapter {
2556
2680
  vendor = groqManifest.vendor;
2557
2681
  init(auth, client) {
@@ -2563,6 +2687,12 @@ var GroqAdapter = class extends OpenAIAdapter {
2563
2687
  client
2564
2688
  );
2565
2689
  }
2690
+ stream(req) {
2691
+ return streamFromOpenAI(this.ensureClient(), req, {
2692
+ knownModels: groqManifest.knownModels,
2693
+ vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR
2694
+ });
2695
+ }
2566
2696
  };
2567
2697
 
2568
2698
  // src/adapters/groq/factory.ts
@@ -2663,6 +2793,10 @@ var huggingfaceManifest = {
2663
2793
  };
2664
2794
 
2665
2795
  // src/adapters/huggingface/adapter.ts
2796
+ var HUGGINGFACE_METADATA_EXTRACTOR = makeMetadataExtractor([
2797
+ { header: "x-inference-provider", key: "provider" },
2798
+ { header: "x-request-id", key: "request_id" }
2799
+ ]);
2666
2800
  var HuggingfaceAdapter = class extends OpenAIAdapter {
2667
2801
  vendor = huggingfaceManifest.vendor;
2668
2802
  init(auth, client) {
@@ -2674,6 +2808,12 @@ var HuggingfaceAdapter = class extends OpenAIAdapter {
2674
2808
  client
2675
2809
  );
2676
2810
  }
2811
+ stream(req) {
2812
+ return streamFromOpenAI(this.ensureClient(), req, {
2813
+ knownModels: huggingfaceManifest.knownModels,
2814
+ vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR
2815
+ });
2816
+ }
2677
2817
  };
2678
2818
 
2679
2819
  // src/adapters/huggingface/factory.ts