@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
  };
@@ -456,7 +460,7 @@ var mapContentBlockToOpenAI = (block) => {
456
460
  if (block.type === "image") {
457
461
  return {
458
462
  image_url: {
459
- url: `data:${block.source.media_type};base64,${block.source.data}`
463
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
460
464
  },
461
465
  type: "image_url"
462
466
  };
@@ -464,12 +468,26 @@ var mapContentBlockToOpenAI = (block) => {
464
468
  if (block.type === "document") {
465
469
  return {
466
470
  file: {
467
- file_data: `data:${block.source.media_type};base64,${block.source.data}`,
471
+ file_data: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`,
468
472
  filename: block.name ?? "document.pdf"
469
473
  },
470
474
  type: "file"
471
475
  };
472
476
  }
477
+ if (block.type === "audio") {
478
+ return {
479
+ input_audio: { data: block.source.data, format: block.source.format },
480
+ type: "input_audio"
481
+ };
482
+ }
483
+ if (block.type === "video") {
484
+ return {
485
+ type: "video_url",
486
+ video_url: {
487
+ url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
488
+ }
489
+ };
490
+ }
473
491
  if (block.type === "text") {
474
492
  return { text: block.content, type: "text" };
475
493
  }
@@ -479,17 +497,20 @@ var mapOpenAIContent = (msg) => {
479
497
  if (typeof msg.content === "string") {
480
498
  return msg.content;
481
499
  }
482
- const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document");
500
+ const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document" || block.type === "audio" || block.type === "video");
483
501
  if (!hasMedia) {
484
502
  return null;
485
503
  }
486
504
  return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
487
505
  };
488
- var buildRequestBody = (params) => {
506
+ var buildRequestBody = (params, capabilityModel = params.model) => {
489
507
  const messages = convertToolResultMessages(params.messages.map((msg) => ({
490
508
  content: mapOpenAIContent(msg),
491
509
  role: msg.role
492
510
  })), params);
511
+ if (params.systemPrompt) {
512
+ messages.unshift({ content: params.systemPrompt, role: "system" });
513
+ }
493
514
  const body = {
494
515
  messages,
495
516
  model: params.model,
@@ -510,12 +531,12 @@ var buildRequestBody = (params) => {
510
531
  body.parallel_tool_calls = params.parallelToolCalls;
511
532
  }
512
533
  }
513
- if (isOpenAIReasoningModel(params.model)) {
534
+ if (isOpenAIReasoningModel(capabilityModel)) {
514
535
  if (typeof params.maxTokens === "number") {
515
536
  body.max_completion_tokens = params.maxTokens;
516
537
  }
517
538
  if (params.reasoning) {
518
- const effort = openaiEffortValue(params.model, params.reasoning);
539
+ const effort = openaiEffortValue(capabilityModel, params.reasoning);
519
540
  if (effort)
520
541
  body.reasoning_effort = effort;
521
542
  }
@@ -576,8 +597,12 @@ var extractUsage = (parsedUsage) => {
576
597
  const cached = parsedUsage.cached_tokens ?? 0;
577
598
  return {
578
599
  cacheReadInputTokens: cached,
600
+ cacheWriteInputTokens: parsedUsage.cache_write_tokens || undefined,
601
+ costCredits: parsedUsage.cost,
579
602
  inputTokens: Math.max(0, prompt - cached),
580
- outputTokens: parsedUsage.completion_tokens ?? 0
603
+ outputTokens: parsedUsage.completion_tokens ?? 0,
604
+ reasoningTokens: parsedUsage.reasoning_tokens || undefined,
605
+ upstreamInferenceCostCredits: parsedUsage.upstream_inference_cost || undefined
581
606
  };
582
607
  };
583
608
  var resolveToolCallIndex = (toolCall) => {
@@ -629,9 +654,50 @@ var processDelta = function* (delta, pendingToolCalls) {
629
654
  if (typeof delta.content === "string") {
630
655
  yield { content: delta.content, type: "text" };
631
656
  }
657
+ if (isRecord(delta.audio)) {
658
+ const audio = delta.audio;
659
+ if (typeof audio.data === "string" || typeof audio.transcript === "string") {
660
+ yield {
661
+ audioId: typeof audio.id === "string" ? audio.id : undefined,
662
+ data: typeof audio.data === "string" ? audio.data : "",
663
+ format: typeof audio.format === "string" ? audio.format : "pcm16",
664
+ transcript: typeof audio.transcript === "string" ? audio.transcript : undefined,
665
+ type: "audio"
666
+ };
667
+ }
668
+ }
632
669
  if (isRecordArray(delta.tool_calls)) {
633
670
  processToolCallDeltas(delta.tool_calls, pendingToolCalls);
634
671
  }
672
+ if (Array.isArray(delta.annotations)) {
673
+ for (const annotation of delta.annotations) {
674
+ if (!isRecord(annotation) || annotation.type !== "url_citation")
675
+ continue;
676
+ const citation = isRecord(annotation.url_citation) ? annotation.url_citation : annotation;
677
+ if (typeof citation.url !== "string")
678
+ continue;
679
+ yield {
680
+ content: typeof citation.content === "string" ? citation.content : undefined,
681
+ endIndex: typeof citation.end_index === "number" ? citation.end_index : undefined,
682
+ startIndex: typeof citation.start_index === "number" ? citation.start_index : undefined,
683
+ title: typeof citation.title === "string" ? citation.title : undefined,
684
+ type: "citation",
685
+ url: citation.url
686
+ };
687
+ }
688
+ }
689
+ };
690
+ var narrowResponseMetadata = (parsed) => {
691
+ const providerMetadata = isRecord(parsed.openrouter_metadata) ? parsed.openrouter_metadata : undefined;
692
+ const generationId = typeof parsed.id === "string" ? parsed.id : undefined;
693
+ const model = typeof parsed.model === "string" ? parsed.model : undefined;
694
+ const serviceTier = typeof parsed.service_tier === "string" ? parsed.service_tier : undefined;
695
+ const selected = providerMetadata && isRecord(providerMetadata.endpoints) ? providerMetadata.endpoints.available : undefined;
696
+ const selectedEndpoint = Array.isArray(selected) ? selected.find((entry) => isRecord(entry) && entry.selected === true) : undefined;
697
+ const provider = isRecord(selectedEndpoint) && typeof selectedEndpoint.provider === "string" ? selectedEndpoint.provider : undefined;
698
+ if (!providerMetadata && !generationId && !model && !serviceTier)
699
+ return;
700
+ return { generationId, model, provider, providerMetadata, serviceTier };
635
701
  };
636
702
  var processChoice = function* (choice, pendingToolCalls) {
637
703
  const delta = isRecord(choice.delta) ? choice.delta : null;
@@ -650,13 +716,25 @@ var narrowUsageRecord = (parsed) => {
650
716
  const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
651
717
  const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
652
718
  const cachedTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cached_tokens === "number" ? usage.prompt_tokens_details.cached_tokens : 0;
653
- return extractUsage({
719
+ const cacheWriteTokens = isRecord(usage.prompt_tokens_details) && typeof usage.prompt_tokens_details.cache_write_tokens === "number" ? usage.prompt_tokens_details.cache_write_tokens : 0;
720
+ const reasoningTokens = isRecord(usage.completion_tokens_details) && typeof usage.completion_tokens_details.reasoning_tokens === "number" ? usage.completion_tokens_details.reasoning_tokens : 0;
721
+ const cost = typeof usage.cost === "number" ? usage.cost : undefined;
722
+ const upstreamInferenceCost = isRecord(usage.cost_details) && typeof usage.cost_details.upstream_inference_cost === "number" ? usage.cost_details.upstream_inference_cost : undefined;
723
+ const normalized = extractUsage({
724
+ cache_write_tokens: cacheWriteTokens,
654
725
  cached_tokens: cachedTokens,
655
726
  completion_tokens: completionTokens,
656
- prompt_tokens: promptTokens
727
+ cost,
728
+ prompt_tokens: promptTokens,
729
+ reasoning_tokens: reasoningTokens,
730
+ upstream_inference_cost: upstreamInferenceCost
657
731
  });
732
+ if (isRecord(usage.server_tool_use)) {
733
+ normalized.serverToolUse = Object.fromEntries(Object.entries(usage.server_tool_use).filter((entry) => typeof entry[1] === "number"));
734
+ }
735
+ return normalized;
658
736
  };
659
- var processSSELine = function* (line, pendingToolCalls, currentUsage) {
737
+ var processSSELine = function* (line, pendingToolCalls, providerName) {
660
738
  const trimmed = line.trim();
661
739
  if (!trimmed || !trimmed.startsWith("data: ")) {
662
740
  return;
@@ -664,7 +742,6 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
664
742
  const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
665
743
  if (data === DONE_SENTINEL) {
666
744
  yield* flushPendingToolCalls(pendingToolCalls);
667
- yield { type: "done", usage: currentUsage };
668
745
  return;
669
746
  }
670
747
  let parsed;
@@ -673,10 +750,28 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
673
750
  } catch {
674
751
  return;
675
752
  }
753
+ if (isRecord(parsed.error)) {
754
+ const error = parsed.error;
755
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
756
+ const status = typeof error.code === "number" ? error.code : null;
757
+ const type = metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : typeof error.error_type === "string" ? error.error_type : null;
758
+ throw new ProviderError({
759
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
760
+ metadata: metadata2,
761
+ provider: providerName,
762
+ retryable: status === 408 || status === 409 || status === 425 || status === 429 || status !== null && status >= 500,
763
+ status,
764
+ type
765
+ });
766
+ }
676
767
  const usageUpdate = narrowUsageRecord(parsed);
677
768
  if (usageUpdate) {
678
769
  yield { type: "usage_update", usage: usageUpdate };
679
770
  }
771
+ const metadata = narrowResponseMetadata(parsed);
772
+ if (metadata) {
773
+ yield { metadata, type: "response_metadata" };
774
+ }
680
775
  const { choices } = parsed;
681
776
  if (!isRecordArray(choices)) {
682
777
  return;
@@ -685,21 +780,46 @@ var processSSELine = function* (line, pendingToolCalls, currentUsage) {
685
780
  if (!firstChoice) {
686
781
  return;
687
782
  }
783
+ if (isRecord(firstChoice.error)) {
784
+ const error = firstChoice.error;
785
+ const metadata2 = isRecord(error.metadata) ? error.metadata : undefined;
786
+ const status = typeof error.code === "number" ? error.code : null;
787
+ throw new ProviderError({
788
+ message: typeof error.message === "string" ? error.message : "OpenRouter stream failed",
789
+ metadata: metadata2,
790
+ provider: providerName,
791
+ retryable: status === 429 || status !== null && status >= 500,
792
+ status,
793
+ type: metadata2 && typeof metadata2.error_type === "string" ? metadata2.error_type : null
794
+ });
795
+ }
688
796
  yield* processChoice(firstChoice, pendingToolCalls);
689
797
  };
690
798
  var isUsageUpdate = (chunk) => chunk.type === "usage_update";
691
- var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
692
- const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
799
+ var collectYieldableChunks = (line, pendingToolCalls, usageRef, metadataRef, providerName) => {
800
+ const allChunks = Array.from(processSSELine(line, pendingToolCalls, providerName));
693
801
  const usageChunks = allChunks.filter(isUsageUpdate);
694
802
  const lastUsage = usageChunks.at(NOT_FOUND);
695
803
  if (lastUsage) {
696
804
  usageRef.current = lastUsage.usage;
697
805
  }
698
- return allChunks.filter((chunk) => !isUsageUpdate(chunk));
806
+ const metadataChunks = allChunks.filter((chunk) => chunk.type === "response_metadata");
807
+ const lastMetadata = metadataChunks.at(NOT_FOUND);
808
+ if (lastMetadata && "metadata" in lastMetadata) {
809
+ metadataRef.current = {
810
+ ...metadataRef.current,
811
+ ...lastMetadata.metadata,
812
+ providerMetadata: {
813
+ ...metadataRef.current?.providerMetadata,
814
+ ...lastMetadata.metadata.providerMetadata
815
+ }
816
+ };
817
+ }
818
+ return allChunks.filter((chunk) => !isUsageUpdate(chunk) && chunk.type !== "response_metadata");
699
819
  };
700
- var processSSELines = function* (lines, pendingToolCalls, usageRef) {
820
+ var processSSELines = function* (lines, pendingToolCalls, usageRef, metadataRef, providerName) {
701
821
  for (const line of lines) {
702
- yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
822
+ yield* collectYieldableChunks(line, pendingToolCalls, usageRef, metadataRef, providerName);
703
823
  }
704
824
  };
705
825
  var processStreamValue = (value, decoder, state) => {
@@ -712,51 +832,67 @@ var processStreamValue = (value, decoder, state) => {
712
832
  var drainReader = async function* (reader, decoder, state, signal) {
713
833
  for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
714
834
  const lines = processStreamValue(result.value, decoder, state);
715
- yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
835
+ yield* processSSELines(lines, state.pendingToolCalls, state.usageRef, state.metadataRef, state.providerName);
716
836
  }
717
837
  };
718
- var parseSSEStream = async function* (body, signal) {
838
+ var parseSSEStream = async function* (body, initialMetadata, providerName = "openai", signal) {
719
839
  const reader = body.getReader();
720
840
  const decoder = new TextDecoder;
721
841
  const state = {
722
842
  buffer: "",
843
+ metadataRef: { current: initialMetadata },
723
844
  pendingToolCalls: new Map,
845
+ providerName,
724
846
  usageRef: { current: undefined }
725
847
  };
726
848
  try {
727
849
  yield* drainReader(reader, decoder, state, signal);
728
- yield { type: "done", usage: state.usageRef.current };
850
+ yield {
851
+ metadata: state.metadataRef.current,
852
+ type: "done",
853
+ usage: state.usageRef.current
854
+ };
729
855
  } finally {
730
856
  reader.releaseLock();
731
857
  }
732
858
  };
733
- var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
859
+ var fetchOpenAIStream = async function* (baseUrl, apiKey, body, fetchImpl, headers, providerName, signal) {
734
860
  const target = `${baseUrl}/v1/chat/completions`;
735
- const response = await fetch(target, {
861
+ const requestHeaders = new Headers(headers);
862
+ requestHeaders.set("Authorization", `Bearer ${apiKey}`);
863
+ requestHeaders.set("Content-Type", "application/json");
864
+ const response = await fetchImpl(target, {
736
865
  ...h2IfHttps(target),
737
866
  body: JSON.stringify(body),
738
- headers: {
739
- Authorization: `Bearer ${apiKey}`,
740
- "Content-Type": "application/json"
741
- },
867
+ headers: requestHeaders,
742
868
  method: "POST",
743
869
  signal
744
870
  });
745
871
  if (!response.ok) {
746
872
  const errorText = await response.text();
747
- throw ProviderError.fromResponse("openai", response.status, errorText);
873
+ throw ProviderError.fromResponse(providerName, response.status, errorText);
748
874
  }
749
875
  if (!response.body) {
750
876
  throw new ProviderError({
751
- message: "OpenAI API returned no response body",
752
- provider: "openai",
877
+ message: `${providerName} API returned no response body`,
878
+ provider: providerName,
753
879
  retryable: true
754
880
  });
755
881
  }
756
- yield* parseSSEStream(response.body, signal);
882
+ yield* parseSSEStream(response.body, {
883
+ generationId: response.headers.get("X-Generation-Id") ?? undefined,
884
+ providerMetadata: {
885
+ cacheAge: response.headers.get("X-OpenRouter-Cache-Age") ?? undefined,
886
+ cacheSourceId: response.headers.get("X-OpenRouter-Cache-Source-Id") ?? undefined,
887
+ cacheStatus: response.headers.get("X-OpenRouter-Cache-Status") ?? undefined,
888
+ cacheTtl: response.headers.get("X-OpenRouter-Cache-TTL") ?? undefined
889
+ }
890
+ }, providerName, signal);
757
891
  };
758
892
  var openai = (config2) => {
759
893
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL;
894
+ const fetchImpl = config2.fetch ?? globalThis.fetch;
895
+ const providerName = config2.providerName ?? "openai";
760
896
  if (!config2.apiKey && !config2.tokenSource) {
761
897
  throw new Error("openai() requires either apiKey or tokenSource");
762
898
  }
@@ -766,19 +902,24 @@ var openai = (config2) => {
766
902
  }
767
903
  return config2.apiKey;
768
904
  };
905
+ const resolveHeaders = async (params) => typeof config2.headers === "function" ? await config2.headers(params) : config2.headers ?? {};
769
906
  return instrumentAIProvider({
770
907
  stream: (params) => {
771
- const body = buildRequestBody(params);
908
+ const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model);
909
+ const body = config2.transformRequestBody ? config2.transformRequestBody(openaiBody, params) : openaiBody;
772
910
  return async function* () {
773
- const apiKey = await resolveKey();
774
- yield* fetchOpenAIStream(baseUrl, apiKey, body, params.signal);
911
+ const [apiKey, headers] = await Promise.all([
912
+ resolveKey(),
913
+ resolveHeaders(params)
914
+ ]);
915
+ yield* fetchOpenAIStream(baseUrl, apiKey, body, fetchImpl, headers, providerName, params.signal);
775
916
  }();
776
917
  }
777
- }, "openai");
918
+ }, providerName);
778
919
  };
779
920
  export {
780
921
  openai
781
922
  };
782
923
 
783
- //# debugId=358764C23D44251664756E2164756E21
924
+ //# debugId=89626FDD4663206064756E2164756E21
784
925
  //# sourceMappingURL=openai.js.map