@absolutejs/ai 0.0.49 → 0.0.51

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.
Files changed (39) hide show
  1. package/README.md +127 -0
  2. package/dist/ai/client/index.js +30 -1
  3. package/dist/ai/client/index.js.map +5 -5
  4. package/dist/ai/index.js +1174 -92
  5. package/dist/ai/index.js.map +18 -16
  6. package/dist/ai/providers/anthropic.js +105 -26
  7. package/dist/ai/providers/anthropic.js.map +5 -5
  8. package/dist/ai/providers/gemini.js +37 -4
  9. package/dist/ai/providers/gemini.js.map +5 -5
  10. package/dist/ai/providers/ollama.js +7 -3
  11. package/dist/ai/providers/ollama.js.map +4 -4
  12. package/dist/ai/providers/openai.js +177 -36
  13. package/dist/ai/providers/openai.js.map +5 -5
  14. package/dist/ai/providers/openaiCompatible.js +177 -36
  15. package/dist/ai/providers/openaiCompatible.js.map +5 -5
  16. package/dist/ai/providers/openaiResponses.js +147 -33
  17. package/dist/ai/providers/openaiResponses.js.map +5 -5
  18. package/dist/ai/providers/openrouter.js +2607 -0
  19. package/dist/ai/providers/openrouter.js.map +18 -0
  20. package/dist/angular/ai/index.js +30 -1
  21. package/dist/angular/ai/index.js.map +5 -5
  22. package/dist/react/ai/index.js +30 -1
  23. package/dist/react/ai/index.js.map +5 -5
  24. package/dist/src/ai/client/actions.d.ts +53 -0
  25. package/dist/src/ai/errors/providerError.d.ts +3 -0
  26. package/dist/src/ai/generateAI.d.ts +4 -1
  27. package/dist/src/ai/index.d.ts +2 -0
  28. package/dist/src/ai/providers/openai.d.ts +7 -3
  29. package/dist/src/ai/providers/openaiResponses.d.ts +9 -4
  30. package/dist/src/ai/providers/openrouter.d.ts +157 -0
  31. package/dist/src/ai/providers/openrouterClient.d.ts +276 -0
  32. package/dist/src/ai/streamAIWithTools.d.ts +6 -0
  33. package/dist/svelte/ai/index.js +30 -1
  34. package/dist/svelte/ai/index.js.map +5 -5
  35. package/dist/types/ai.d.ts +116 -8
  36. package/dist/types/anthropic.d.ts +13 -5
  37. package/dist/vue/ai/index.js +30 -1
  38. package/dist/vue/ai/index.js.map +5 -5
  39. package/package.json +9 -2
@@ -6,7 +6,8 @@ var PROVIDER_STATUS_PAGES = {
6
6
  anthropic: "https://status.claude.com",
7
7
  gemini: "https://status.cloud.google.com",
8
8
  google: "https://status.cloud.google.com",
9
- openai: "https://status.openai.com"
9
+ openai: "https://status.openai.com",
10
+ openrouter: "https://status.openrouter.ai"
10
11
  };
11
12
  var RETRYABLE_STATUSES = new Set([
12
13
  408,
@@ -42,6 +43,7 @@ class ProviderError extends Error {
42
43
  status;
43
44
  type;
44
45
  retryable;
46
+ metadata;
45
47
  statusPageUrl;
46
48
  constructor(init) {
47
49
  super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
@@ -50,6 +52,7 @@ class ProviderError extends Error {
50
52
  this.status = init.status ?? null;
51
53
  this.type = init.type ?? null;
52
54
  this.retryable = init.retryable;
55
+ this.metadata = init.metadata;
53
56
  this.statusPageUrl = providerStatusPage(init.provider);
54
57
  }
55
58
  static fromResponse(provider, status, body, type) {
@@ -222,7 +225,8 @@ var withResilience = (provider, providerName = "unknown") => {
222
225
  yield* attempt(params, attemptNo + 1);
223
226
  return;
224
227
  }
225
- noteFailure(providerName, providerError);
228
+ if (providerError.retryable)
229
+ noteFailure(providerName, providerError);
226
230
  throw providerError;
227
231
  }
228
232
  };
@@ -398,7 +402,7 @@ var mapBlockToResponsesFormat = (block) => {
398
402
  if (block.type === "image") {
399
403
  return {
400
404
  image_url: {
401
- url: `data:${block.source.media_type};base64,${block.source.data}`
405
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
402
406
  },
403
407
  type: "input_image"
404
408
  };
@@ -406,12 +410,24 @@ var mapBlockToResponsesFormat = (block) => {
406
410
  if (block.type === "document") {
407
411
  return {
408
412
  file: {
409
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
413
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
410
414
  filename: block.name ?? "document.pdf"
411
415
  },
412
416
  type: "input_file"
413
417
  };
414
418
  }
419
+ if (block.type === "audio") {
420
+ return {
421
+ input_audio: { data: block.source.data, format: block.source.format },
422
+ type: "input_audio"
423
+ };
424
+ }
425
+ if (block.type === "video") {
426
+ return {
427
+ type: "input_video",
428
+ video_url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
429
+ };
430
+ }
415
431
  return null;
416
432
  };
417
433
  var mapContentToResponsesFormat = (content) => {
@@ -423,7 +439,12 @@ var mapContentToResponsesFormat = (content) => {
423
439
  };
424
440
  var hasToolBlocks = (content) => content.some((block) => block.type === "tool_use" || block.type === "tool_result");
425
441
  var convertToolBlock = (block) => {
442
+ if (block.type === "provider_data" && block.provider === "openrouter") {
443
+ return { ...block.data };
444
+ }
426
445
  if (block.type === "tool_use") {
446
+ if (block.providerData)
447
+ return { ...block.providerData };
427
448
  return {
428
449
  arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
429
450
  call_id: block.id,
@@ -475,7 +496,7 @@ var buildTools = (tools, isImageModel) => {
475
496
  }
476
497
  return result.length > 0 ? result : undefined;
477
498
  };
478
- var buildRequestBody = (params, isImageModel) => {
499
+ var buildRequestBody = (params, isImageModel, capabilityModel = params.model) => {
479
500
  const body = {
480
501
  input: buildInput(params.messages),
481
502
  model: params.model,
@@ -527,8 +548,8 @@ var buildRequestBody = (params, isImageModel) => {
527
548
  };
528
549
  }
529
550
  }
530
- if (params.reasoning && isOpenAIReasoningModel(params.model)) {
531
- const effort = openaiEffortValue(params.model, params.reasoning);
551
+ if (params.reasoning && isOpenAIReasoningModel(capabilityModel)) {
552
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
532
553
  if (effort) {
533
554
  body.reasoning = {
534
555
  effort,
@@ -559,11 +580,32 @@ var extractUsage = (response) => {
559
580
  const { usage } = response;
560
581
  const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
561
582
  const cached = isRecord(usage.input_tokens_details) && typeof usage.input_tokens_details.cached_tokens === "number" ? usage.input_tokens_details.cached_tokens : 0;
562
- return {
583
+ const outputDetails = isRecord(usage.output_tokens_details) ? usage.output_tokens_details : undefined;
584
+ const inputDetails = isRecord(usage.input_tokens_details) ? usage.input_tokens_details : undefined;
585
+ const costDetails = isRecord(usage.cost_details) ? usage.cost_details : undefined;
586
+ const normalized = {
563
587
  cacheReadInputTokens: cached,
588
+ cacheWriteInputTokens: inputDetails && typeof inputDetails.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : undefined,
589
+ costCredits: typeof usage.cost === "number" ? usage.cost : undefined,
564
590
  inputTokens: Math.max(0, input - cached),
565
- outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
591
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
592
+ reasoningTokens: outputDetails && typeof outputDetails.reasoning_tokens === "number" ? outputDetails.reasoning_tokens : undefined,
593
+ upstreamInferenceCostCredits: costDetails && typeof costDetails.upstream_inference_cost === "number" ? costDetails.upstream_inference_cost : undefined
566
594
  };
595
+ if (isRecord(usage.server_tool_use)) {
596
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
597
+ }
598
+ return normalized;
599
+ };
600
+ var extractResponseMetadata = (response) => {
601
+ const providerMetadata = isRecord(response.openrouter_metadata) ? response.openrouter_metadata : undefined;
602
+ const generationId = typeof response.id === "string" ? response.id : undefined;
603
+ const model = typeof response.model === "string" ? response.model : undefined;
604
+ const provider = typeof response.provider === "string" ? response.provider : undefined;
605
+ const serviceTier = typeof response.service_tier === "string" ? response.service_tier : undefined;
606
+ if (!providerMetadata && !generationId && !model && !provider && !serviceTier)
607
+ return;
608
+ return { generationId, model, provider, providerMetadata, serviceTier };
567
609
  };
568
610
  var extractMimeFormat = (mimeType) => {
569
611
  if (typeof mimeType !== "string") {
@@ -620,6 +662,7 @@ var processFunctionCallArgumentsDone = function* (parsed, pendingCalls) {
620
662
  id: callId || pending?.callId || itemId,
621
663
  input: parseToolInput(args),
622
664
  name,
665
+ providerData: pending?.providerData ? { ...pending.providerData, arguments: args } : undefined,
623
666
  type: "tool_use"
624
667
  };
625
668
  };
@@ -638,9 +681,21 @@ var processOutputItemAdded = (parsed, pendingCalls) => {
638
681
  pendingCalls.set(itemId, {
639
682
  arguments: "",
640
683
  callId,
641
- name
684
+ name,
685
+ providerData: { ...item }
642
686
  });
643
687
  };
688
+ var processOutputItemDone = function* (parsed) {
689
+ if (!isRecord(parsed.item) || typeof parsed.item.type !== "string")
690
+ return;
691
+ if (!parsed.item.type.startsWith("openrouter:"))
692
+ return;
693
+ yield {
694
+ data: { ...parsed.item },
695
+ provider: "openrouter",
696
+ type: "provider_event"
697
+ };
698
+ };
644
699
  var isCompletedImageGeneration = (item) => item.type === "image_generation_call" && item.status === "completed" && typeof item.result === "string" && item.result !== "";
645
700
  var buildImageChunk = (item) => ({
646
701
  data: typeof item.result === "string" ? item.result : "",
@@ -656,6 +711,30 @@ var extractImageFromOutput = function* (output) {
656
711
  yield buildImageChunk(item);
657
712
  }
658
713
  };
714
+ var extractCitationsFromOutput = function* (output) {
715
+ for (const item of output) {
716
+ if (!isRecordArray(item.content))
717
+ continue;
718
+ for (const content of item.content) {
719
+ if (!Array.isArray(content.annotations))
720
+ continue;
721
+ for (const annotation of content.annotations) {
722
+ if (!isRecord(annotation) || annotation.type !== "url_citation")
723
+ continue;
724
+ if (typeof annotation.url !== "string")
725
+ continue;
726
+ yield {
727
+ content: typeof annotation.content === "string" ? annotation.content : undefined,
728
+ endIndex: typeof annotation.end_index === "number" ? annotation.end_index : undefined,
729
+ startIndex: typeof annotation.start_index === "number" ? annotation.start_index : undefined,
730
+ title: typeof annotation.title === "string" ? annotation.title : undefined,
731
+ type: "citation",
732
+ url: annotation.url
733
+ };
734
+ }
735
+ }
736
+ }
737
+ };
659
738
  var processCompleted = function* (parsed) {
660
739
  if (!isRecord(parsed.response)) {
661
740
  yield { type: "done", usage: undefined };
@@ -663,12 +742,26 @@ var processCompleted = function* (parsed) {
663
742
  }
664
743
  const { response } = parsed;
665
744
  const usage = extractUsage(response);
745
+ const metadata = extractResponseMetadata(response);
666
746
  if (isRecordArray(response.output)) {
747
+ yield* extractCitationsFromOutput(response.output);
667
748
  yield* extractImageFromOutput(response.output);
668
749
  }
669
- yield { type: "done", usage };
750
+ yield { metadata, type: "done", usage };
670
751
  };
671
- var processSSEEvent = function* (eventType, parsed, pendingCalls) {
752
+ var responseFailure = (eventType, parsed, providerName) => {
753
+ const response = isRecord(parsed.response) ? parsed.response : parsed;
754
+ const error = isRecord(response.error) ? response.error : undefined;
755
+ const type = typeof response.error_type === "string" ? response.error_type : error && typeof error.code === "string" ? error.code : eventType;
756
+ return new ProviderError({
757
+ message: error && typeof error.message === "string" ? error.message : `OpenRouter Responses API: ${eventType}`,
758
+ metadata: response,
759
+ provider: providerName,
760
+ retryable: type === "rate_limit_exceeded" || type === "provider_overloaded" || type === "provider_unavailable" || type === "server",
761
+ type
762
+ });
763
+ };
764
+ var processSSEEvent = function* (eventType, parsed, pendingCalls, providerName) {
672
765
  switch (eventType) {
673
766
  case "response.reasoning_summary_text.delta": {
674
767
  const delta = typeof parsed.delta === "string" ? parsed.delta : "";
@@ -689,6 +782,9 @@ var processSSEEvent = function* (eventType, parsed, pendingCalls) {
689
782
  case "response.output_item.added":
690
783
  processOutputItemAdded(parsed, pendingCalls);
691
784
  break;
785
+ case "response.output_item.done":
786
+ yield* processOutputItemDone(parsed);
787
+ break;
692
788
  case "response.function_call_arguments.delta":
693
789
  processFunctionCallArgumentsDelta(parsed, pendingCalls);
694
790
  break;
@@ -699,11 +795,10 @@ var processSSEEvent = function* (eventType, parsed, pendingCalls) {
699
795
  yield* processCompleted(parsed);
700
796
  break;
701
797
  case "response.failed":
702
- case "response.incomplete": {
703
- const respObj = isRecord(parsed.response) ? parsed.response : parsed;
704
- const errMsg = isRecord(respObj.error) && typeof respObj.error.message === "string" ? respObj.error.message : `OpenAI Responses API: ${eventType}`;
705
- throw new Error(errMsg);
706
- }
798
+ case "response.incomplete":
799
+ case "response.error":
800
+ case "error":
801
+ throw responseFailure(eventType, parsed, providerName);
707
802
  }
708
803
  };
709
804
  var flushSSEBuffer = function* (state) {
@@ -712,7 +807,7 @@ var flushSSEBuffer = function* (state) {
712
807
  }
713
808
  const parsed = parseJSON(state.buffer);
714
809
  if (parsed) {
715
- yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls);
810
+ yield* processSSEEvent(state.currentEvent, parsed, state.pendingCalls, state.providerName);
716
811
  }
717
812
  state.currentEvent = "";
718
813
  state.buffer = "";
@@ -750,41 +845,47 @@ var drainReader = async function* (reader, decoder, state, signal) {
750
845
  yield* processSSELines([textBuffer, ""], state);
751
846
  }
752
847
  };
753
- var parseSSEStream = async function* (body, signal) {
848
+ var parseSSEStream = async function* (body, providerName, signal) {
754
849
  const reader = body.getReader();
755
850
  const decoder = new TextDecoder;
756
851
  const state = {
757
852
  buffer: "",
758
853
  currentEvent: "",
759
854
  pendingCalls: new Map,
760
- usage: undefined
855
+ usage: undefined,
856
+ providerName
761
857
  };
762
858
  try {
763
859
  yield* drainReader(reader, decoder, state, signal);
860
+ yield* flushSSEBuffer(state);
764
861
  } finally {
765
862
  reader.releaseLock();
766
863
  }
767
864
  };
768
- var fetchResponsesStream = async function* (baseUrl, apiKey, body, signal) {
865
+ var fetchResponsesStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
769
866
  const target = `${baseUrl}/v1/responses`;
770
- const response = await fetch(target, {
867
+ const requestHeaders = new Headers(headers);
868
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
869
+ requestHeaders.set("Content-Type", "application/json");
870
+ const response = await fetchImpl(target, {
771
871
  ...h2IfHttps(target),
772
872
  body: JSON.stringify(body),
773
- headers: {
774
- Authorization: `Bearer ${apiKey}`,
775
- "Content-Type": "application/json"
776
- },
873
+ headers: requestHeaders,
777
874
  method: "POST",
778
875
  signal
779
876
  });
780
877
  if (!response.ok) {
781
878
  const errorText = await response.text();
782
- throw new Error(`OpenAI Responses API error ${response.status}: ${errorText}`);
879
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
783
880
  }
784
881
  if (!response.body) {
785
- throw new Error("OpenAI Responses API returned no response body");
882
+ throw new ProviderError({
883
+ message: `${providerName} Responses API returned no response body`,
884
+ provider: providerName,
885
+ retryable: true
886
+ });
786
887
  }
787
- yield* parseSSEStream(response.body, signal);
888
+ yield* parseSSEStream(response.body, providerName, signal);
788
889
  };
789
890
  var resolveImageModels = (imageModels) => {
790
891
  if (!imageModels) {
@@ -796,19 +897,32 @@ var resolveImageModels = (imageModels) => {
796
897
  return new Set(imageModels);
797
898
  };
798
899
  var openaiResponses = (config2) => {
900
+ if (!config2.apiKey && !config2.tokenSource)
901
+ throw new Error("openaiResponses() requires either apiKey or tokenSource");
799
902
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
903
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
800
904
  const imageModels = resolveImageModels(config2.imageModels);
905
+ const providerName = config2.providerName ?? "openai-responses";
906
+ const resolveKey = async () => config2.tokenSource ? await Promise.resolve(config2.tokenSource()) : config2.apiKey;
907
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
801
908
  return instrumentAIProvider({
802
909
  stream: (params) => {
803
910
  const isImageModel = imageModels.has(params.model);
804
- const body = buildRequestBody(params, isImageModel);
805
- return fetchResponsesStream(baseUrl, config2.apiKey, body, params.signal);
911
+ const builtBody = buildRequestBody(params, isImageModel, config2.modelForCapabilities?.(params.model) ?? params.model);
912
+ const body = config2.transformRequestBody ? config2.transformRequestBody(builtBody, params) : builtBody;
913
+ return async function* () {
914
+ const [apiKey, headers] = await Promise.all([
915
+ resolveKey(),
916
+ resolveHeaders(params)
917
+ ]);
918
+ yield* fetchResponsesStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
919
+ }();
806
920
  }
807
- }, "openai-responses");
921
+ }, providerName);
808
922
  };
809
923
  export {
810
924
  openaiResponses
811
925
  };
812
926
 
813
- //# debugId=4A6E4265943028D264756E2164756E21
927
+ //# debugId=6FB19A09EAF4F0BB64756E2164756E21
814
928
  //# sourceMappingURL=openaiResponses.js.map