@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.
package/dist/index.cjs CHANGED
@@ -130,9 +130,22 @@ async function* streamFromOpenAI(client, req, options) {
130
130
  const param = options.tokensParam ?? (needsMaxCompletionTokens(req.model) ? "max_completion_tokens" : "max_tokens");
131
131
  body[param] = effectiveMaxTokens;
132
132
  }
133
+ const t0 = process.hrtime.bigint();
134
+ let t1;
135
+ let t2;
133
136
  let stream;
137
+ let responseHeaders;
134
138
  try {
135
- stream = await client.chat.completions.create(body, { signal: req.signal });
139
+ const apiPromise = client.chat.completions.create(body, { signal: req.signal });
140
+ const maybeWithResponse = apiPromise.withResponse;
141
+ if (typeof maybeWithResponse === "function") {
142
+ const result = await maybeWithResponse.call(apiPromise);
143
+ stream = result.data;
144
+ responseHeaders = result.response?.headers;
145
+ } else {
146
+ stream = await apiPromise;
147
+ }
148
+ t1 = process.hrtime.bigint();
136
149
  } catch (err) {
137
150
  if (isAbortError(err)) {
138
151
  yield { type: "end", finishReason: "aborted" };
@@ -164,6 +177,7 @@ async function* streamFromOpenAI(client, req, options) {
164
177
  const delta = choice.delta;
165
178
  const finishReason = choice.finish_reason;
166
179
  if (delta?.content) {
180
+ if (t2 === void 0) t2 = process.hrtime.bigint();
167
181
  yield { type: "token", text: delta.content };
168
182
  }
169
183
  if (delta?.tool_calls) {
@@ -227,6 +241,20 @@ async function* streamFromOpenAI(client, req, options) {
227
241
  ...usage ? { usage } : {},
228
242
  ...finalRawReason ? { rawReason: finalRawReason } : {}
229
243
  };
244
+ if (options.vendorMetadataExtractor && responseHeaders) {
245
+ const metadata = options.vendorMetadataExtractor(responseHeaders);
246
+ if (metadata && Object.keys(metadata).length > 0) {
247
+ endEvent.vendorMetadata = metadata;
248
+ }
249
+ }
250
+ if (t1 !== void 0) {
251
+ const t3 = process.hrtime.bigint();
252
+ endEvent.clientTiming = {
253
+ requestToHeadersMs: Number(t1 - t0) / 1e6,
254
+ ...t2 !== void 0 ? { headersToFirstTokenMs: Number(t2 - t1) / 1e6 } : {},
255
+ totalMs: Number(t3 - t0) / 1e6
256
+ };
257
+ }
230
258
  yield endEvent;
231
259
  }
232
260
  function appendOpenAIAssistantToolCall(history, toolCalls) {
@@ -354,6 +382,40 @@ function isAbortError(err) {
354
382
  return e.name === "APIUserAbortError" || e.name === "AbortError" || e.code === "ABORT_ERR" || typeof e.message === "string" && /aborted/i.test(e.message);
355
383
  }
356
384
 
385
+ // src/adapters/_metadata.ts
386
+ function makeMetadataExtractor(fields) {
387
+ return (headers) => {
388
+ const out = {};
389
+ for (const f of fields) {
390
+ const raw = headers.get(f.header);
391
+ if (raw === null || raw === void 0 || raw === "") continue;
392
+ if (f.numeric) {
393
+ const n = Number(raw);
394
+ if (Number.isFinite(n)) out[f.key] = n;
395
+ } else {
396
+ out[f.key] = raw;
397
+ }
398
+ }
399
+ return out;
400
+ };
401
+ }
402
+ function extractMetadata(fields, headers) {
403
+ const headersInstance = headers instanceof Headers ? headers : recordToHeaders(headers);
404
+ return makeMetadataExtractor(fields)(headersInstance);
405
+ }
406
+ function recordToHeaders(record) {
407
+ const h = new Headers();
408
+ for (const [name, value] of Object.entries(record)) {
409
+ if (value === void 0) continue;
410
+ if (Array.isArray(value)) {
411
+ for (const v of value) h.append(name, v);
412
+ } else {
413
+ h.set(name, value);
414
+ }
415
+ }
416
+ return h;
417
+ }
418
+
357
419
  // src/adapters/openai/manifest.ts
358
420
  var OPENAI_KNOWN_MODELS = [
359
421
  {
@@ -455,6 +517,12 @@ var openAIManifest = {
455
517
  };
456
518
 
457
519
  // src/adapters/openai/adapter.ts
520
+ var OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
521
+ { header: "x-request-id", key: "request_id" },
522
+ { header: "openai-processing-ms", key: "processing_ms", numeric: true },
523
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
524
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
525
+ ]);
458
526
  var OpenAIAdapter = class {
459
527
  vendor = openAIManifest.vendor;
460
528
  acceptedAuth = ["apiKey"];
@@ -472,7 +540,8 @@ var OpenAIAdapter = class {
472
540
  }
473
541
  stream(req) {
474
542
  return streamFromOpenAI(this.ensureClient(), req, {
475
- knownModels: openAIManifest.knownModels
543
+ knownModels: openAIManifest.knownModels,
544
+ vendorMetadataExtractor: OPENAI_METADATA_EXTRACTOR
476
545
  });
477
546
  }
478
547
  appendAssistantToolCall(history, toolCalls) {
@@ -704,6 +773,12 @@ var anthropicManifest = {
704
773
  };
705
774
 
706
775
  // src/adapters/anthropic/adapter.ts
776
+ var ANTHROPIC_METADATA_EXTRACTOR = makeMetadataExtractor([
777
+ { header: "request-id", key: "request_id" },
778
+ { header: "anthropic-ratelimit-requests-remaining", key: "requests_remaining", numeric: true },
779
+ { header: "anthropic-ratelimit-input-tokens-remaining", key: "input_tokens_remaining", numeric: true },
780
+ { header: "anthropic-ratelimit-output-tokens-remaining", key: "output_tokens_remaining", numeric: true }
781
+ ]);
707
782
  var AnthropicAdapter = class {
708
783
  vendor = anthropicManifest.vendor;
709
784
  acceptedAuth = ["apiKey"];
@@ -738,11 +813,20 @@ var AnthropicAdapter = class {
738
813
  if (tools) body.tools = tools;
739
814
  if (req.temperature !== void 0) body.temperature = req.temperature;
740
815
  let stream;
816
+ let responseHeaders;
741
817
  try {
742
- stream = await client.messages.create(
818
+ const apiPromise = client.messages.create(
743
819
  body,
744
820
  { signal: req.signal }
745
821
  );
822
+ const maybeWithResponse = apiPromise.withResponse;
823
+ if (typeof maybeWithResponse === "function") {
824
+ const result = await maybeWithResponse.call(apiPromise);
825
+ stream = result.data;
826
+ responseHeaders = result.response?.headers;
827
+ } else {
828
+ stream = await apiPromise;
829
+ }
746
830
  } catch (err) {
747
831
  if (isAbortError2(err)) {
748
832
  yield { type: "end", finishReason: "aborted" };
@@ -853,6 +937,12 @@ var AnthropicAdapter = class {
853
937
  ...usage ? { usage } : {},
854
938
  ...stopReason ? { rawReason: stopReason } : {}
855
939
  };
940
+ if (responseHeaders) {
941
+ const metadata = ANTHROPIC_METADATA_EXTRACTOR(responseHeaders);
942
+ if (Object.keys(metadata).length > 0) {
943
+ endEvent.vendorMetadata = metadata;
944
+ }
945
+ }
856
946
  yield endEvent;
857
947
  }
858
948
  appendAssistantToolCall(history, toolCalls) {
@@ -1645,6 +1735,9 @@ var vertexOpenAIManifest = {
1645
1735
  };
1646
1736
 
1647
1737
  // src/adapters/vertex-openai/adapter.ts
1738
+ var VERTEX_OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
1739
+ { header: "x-request-id", key: "request_id" }
1740
+ ]);
1648
1741
  var VertexOpenAIAdapter = class {
1649
1742
  vendor = vertexOpenAIManifest.vendor;
1650
1743
  acceptedAuth = ["vertexServiceAccount"];
@@ -1697,7 +1790,8 @@ var VertexOpenAIAdapter = class {
1697
1790
  // endpoint predates `max_completion_tokens` and may not recognize it.
1698
1791
  // The model-id heuristic wouldn't catch a partner model named in the
1699
1792
  // OpenAI style (unlikely today, but defensive).
1700
- tokensParam: "max_tokens"
1793
+ tokensParam: "max_tokens",
1794
+ vendorMetadataExtractor: VERTEX_OPENAI_METADATA_EXTRACTOR
1701
1795
  });
1702
1796
  }
1703
1797
  appendAssistantToolCall(history, toolCalls) {
@@ -2155,6 +2249,21 @@ var BedrockAdapter = class {
2155
2249
  ...usage ? { usage } : {},
2156
2250
  ...stopReason ? { rawReason: stopReason } : {}
2157
2251
  };
2252
+ const httpHeaders = response.$metadata?.httpHeaders;
2253
+ if (httpHeaders) {
2254
+ const metadata = extractMetadata(
2255
+ [
2256
+ { header: "x-amzn-bedrock-invocation-latency", key: "invocation_latency_ms", numeric: true },
2257
+ { header: "x-amzn-bedrock-cache-read-input-token-count", key: "cache_read_input_tokens", numeric: true },
2258
+ { header: "x-amzn-bedrock-cache-write-input-token-count", key: "cache_write_input_tokens", numeric: true },
2259
+ { header: "x-amzn-bedrock-trace-id", key: "trace_id" }
2260
+ ],
2261
+ httpHeaders
2262
+ );
2263
+ if (Object.keys(metadata).length > 0) {
2264
+ endEvent.vendorMetadata = metadata;
2265
+ }
2266
+ }
2158
2267
  yield endEvent;
2159
2268
  }
2160
2269
  appendAssistantToolCall(history, toolCalls) {
@@ -2406,6 +2515,14 @@ var azureOpenAIManifest = {
2406
2515
  };
2407
2516
 
2408
2517
  // src/adapters/azure-openai/adapter.ts
2518
+ var AZURE_OPENAI_METADATA_EXTRACTOR = makeMetadataExtractor([
2519
+ { header: "x-ms-region", key: "region" },
2520
+ { header: "x-ms-deployment-name", key: "deployment" },
2521
+ { header: "apim-request-id", key: "request_id" },
2522
+ { header: "azureml-model-session", key: "model_session" },
2523
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
2524
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
2525
+ ]);
2409
2526
  var AzureOpenAIAdapter = class {
2410
2527
  vendor = azureOpenAIManifest.vendor;
2411
2528
  acceptedAuth = ["azureOpenAIApiKey"];
@@ -2440,7 +2557,8 @@ var AzureOpenAIAdapter = class {
2440
2557
  stream(req) {
2441
2558
  return streamFromOpenAI(this.ensureClient(), req, {
2442
2559
  knownModels: azureOpenAIManifest.knownModels,
2443
- tokensParam: "max_completion_tokens"
2560
+ tokensParam: "max_completion_tokens",
2561
+ vendorMetadataExtractor: AZURE_OPENAI_METADATA_EXTRACTOR
2444
2562
  });
2445
2563
  }
2446
2564
  appendAssistantToolCall(history, toolCalls) {
@@ -2565,6 +2683,12 @@ var groqManifest = {
2565
2683
  };
2566
2684
 
2567
2685
  // src/adapters/groq/adapter.ts
2686
+ var GROQ_METADATA_EXTRACTOR = makeMetadataExtractor([
2687
+ { header: "x-request-id", key: "request_id" },
2688
+ { header: "x-groq-region", key: "region" },
2689
+ { header: "x-ratelimit-remaining-requests", key: "requests_remaining", numeric: true },
2690
+ { header: "x-ratelimit-remaining-tokens", key: "tokens_remaining", numeric: true }
2691
+ ]);
2568
2692
  var GroqAdapter = class extends OpenAIAdapter {
2569
2693
  vendor = groqManifest.vendor;
2570
2694
  init(auth, client) {
@@ -2576,6 +2700,12 @@ var GroqAdapter = class extends OpenAIAdapter {
2576
2700
  client
2577
2701
  );
2578
2702
  }
2703
+ stream(req) {
2704
+ return streamFromOpenAI(this.ensureClient(), req, {
2705
+ knownModels: groqManifest.knownModels,
2706
+ vendorMetadataExtractor: GROQ_METADATA_EXTRACTOR
2707
+ });
2708
+ }
2579
2709
  };
2580
2710
 
2581
2711
  // src/adapters/groq/factory.ts
@@ -2676,6 +2806,10 @@ var huggingfaceManifest = {
2676
2806
  };
2677
2807
 
2678
2808
  // src/adapters/huggingface/adapter.ts
2809
+ var HUGGINGFACE_METADATA_EXTRACTOR = makeMetadataExtractor([
2810
+ { header: "x-inference-provider", key: "provider" },
2811
+ { header: "x-request-id", key: "request_id" }
2812
+ ]);
2679
2813
  var HuggingfaceAdapter = class extends OpenAIAdapter {
2680
2814
  vendor = huggingfaceManifest.vendor;
2681
2815
  init(auth, client) {
@@ -2687,6 +2821,12 @@ var HuggingfaceAdapter = class extends OpenAIAdapter {
2687
2821
  client
2688
2822
  );
2689
2823
  }
2824
+ stream(req) {
2825
+ return streamFromOpenAI(this.ensureClient(), req, {
2826
+ knownModels: huggingfaceManifest.knownModels,
2827
+ vendorMetadataExtractor: HUGGINGFACE_METADATA_EXTRACTOR
2828
+ });
2829
+ }
2690
2830
  };
2691
2831
 
2692
2832
  // src/adapters/huggingface/factory.ts