@gammatech/aijsx 0.8.1-dev.2024-05-24.2 → 0.9.1-dev.2024-05-28

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.js CHANGED
@@ -32,26 +32,24 @@ __export(src_exports, {
32
32
  AIFragment: () => AIFragment,
33
33
  AISpanProcessor: () => AISpanProcessor,
34
34
  AnthropicChatCompletion: () => AnthropicChatCompletion,
35
- AnthropicChatCompletionInner: () => AnthropicChatCompletionInner,
36
35
  AnthropicClient: () => import_sdk2.default,
37
36
  AnthropicClientContext: () => AnthropicClientContext,
38
37
  AssistantMessage: () => AssistantMessage,
39
38
  BoundLogger: () => BoundLogger,
40
39
  ChatCompletionError: () => ChatCompletionError,
41
- ClaudeImageBlock: () => ClaudeImageBlock,
42
40
  CombinedLogger: () => CombinedLogger,
43
41
  ConsoleLogger: () => ConsoleLogger,
44
- ContentTypeImage: () => ContentTypeImage,
45
42
  DefaultMaxRetriesContext: () => DefaultMaxRetriesContext,
46
43
  EnrichingSpanProcessor: () => EnrichingSpanProcessor,
47
44
  Fallback: () => Fallback,
45
+ GoogleChatCompletion: () => GoogleChatCompletion,
46
+ GoogleClientContext: () => GoogleClientContext,
47
+ ImagePart: () => ImagePart,
48
48
  LogImplementation: () => LogImplementation,
49
49
  NoopLogImplementation: () => NoopLogImplementation,
50
50
  OpenAIChatCompletion: () => OpenAIChatCompletion,
51
- OpenAIClient: () => import_openai4.OpenAI,
51
+ OpenAIClient: () => import_openai3.OpenAI,
52
52
  OpenAIClientContext: () => OpenAIClientContext,
53
- OpenAIVisionChatCompletion: () => OpenAIVisionChatCompletion,
54
- OpenAIVisionChatCompletionInner: () => OpenAIVisionChatCompletionInner,
55
53
  ParseVariablesError: () => ParseVariablesError,
56
54
  PromptInvalidOutputError: () => PromptInvalidOutputError,
57
55
  Retry: () => Retry,
@@ -59,9 +57,10 @@ __export(src_exports, {
59
57
  SystemMessage: () => SystemMessage,
60
58
  Trace: () => Trace,
61
59
  UserMessage: () => UserMessage,
60
+ VertexAI: () => import_vertexai2.VertexAI,
61
+ anthropicTokenizer: () => anthropicTokenizer,
62
62
  attachedContextSymbol: () => attachedContextSymbol,
63
63
  computeUsage: () => computeUsage,
64
- countAnthropicTokens: () => import_tokenizer4.countTokens,
65
64
  createAIElement: () => createAIElement,
66
65
  createContext: () => createContext,
67
66
  createFunctionChain: () => createFunctionChain,
@@ -69,15 +68,160 @@ __export(src_exports, {
69
68
  createRenderContext: () => createRenderContext,
70
69
  createStreamChain: () => createStreamChain,
71
70
  evaluatePrompt: () => evaluatePrompt,
72
- tokenCountForOpenAIMessage: () => tokenCountForOpenAIMessage,
73
- tokenCountForOpenAIVisionMessage: () => tokenCountForOpenAIVisionMessage,
74
- tokenLimitForChatModel: () => tokenLimitForChatModel,
75
- tokenizer: () => tokenizer,
71
+ openaiTokenizer: () => openaiTokenizer,
72
+ toDebugMessage: () => toDebugMessage,
76
73
  tracing: () => tracing
77
74
  });
78
75
  module.exports = __toCommonJS(src_exports);
79
76
 
80
- // src/chat.tsx
77
+ // src/chat/errors.ts
78
+ var ChatCompletionError = class extends Error {
79
+ constructor(message, chatCompletionRequest, status, shouldRetry4 = false) {
80
+ super(message);
81
+ this.chatCompletionRequest = chatCompletionRequest;
82
+ this.status = status;
83
+ this.shouldRetry = shouldRetry4;
84
+ }
85
+ name = "ChatCompletionError";
86
+ };
87
+
88
+ // src/chat/image.ts
89
+ var IMAGE_RENDERED_PROPS = {
90
+ ImagePart: {
91
+ url: true,
92
+ data: true,
93
+ mediaType: true,
94
+ dimensions: true,
95
+ detail: true
96
+ }
97
+ };
98
+ var ImagePart = (_props) => {
99
+ return null;
100
+ };
101
+ async function fetchImageAndConvertToBase64(url) {
102
+ const response = await fetch(url);
103
+ const contentType = response.headers.get("content-type");
104
+ const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
105
+ if (!contentType || !allowedTypes.includes(contentType)) {
106
+ throw new Error(`Unsupported media type: ${contentType}`);
107
+ }
108
+ const blob = await response.blob();
109
+ const buffer = Buffer.from(await blob.arrayBuffer());
110
+ const base64 = buffer.toString("base64");
111
+ return {
112
+ base64,
113
+ mediaType: contentType
114
+ };
115
+ }
116
+ async function processImageMessageProps(props, downloadUrl) {
117
+ const { dimensions, detail, ...rest } = props;
118
+ if ("url" in rest) {
119
+ if (downloadUrl) {
120
+ const { base64, mediaType } = await fetchImageAndConvertToBase64(rest.url);
121
+ return {
122
+ url: rest.url,
123
+ data: base64,
124
+ mediaType,
125
+ dimensions,
126
+ detail
127
+ };
128
+ }
129
+ return {
130
+ url: rest.url,
131
+ data: null,
132
+ mediaType: null,
133
+ dimensions,
134
+ detail
135
+ };
136
+ }
137
+ if ("data" in rest) {
138
+ return {
139
+ url: null,
140
+ mediaType: rest.mediaType,
141
+ data: rest.data,
142
+ dimensions,
143
+ detail
144
+ };
145
+ }
146
+ throw new Error(
147
+ `Invalid ImageMessageProps: ${JSON.stringify(cleanProps(props))}`
148
+ );
149
+ }
150
+ var cleanProps = (props) => {
151
+ const res = {
152
+ ...props
153
+ };
154
+ if ("data" in res) {
155
+ res.data = res.data.slice(0, 64) + "...";
156
+ }
157
+ return res;
158
+ };
159
+
160
+ // src/chat/ChatMessage.ts
161
+ var toDebugMessage = (message) => {
162
+ if (message.role === "assistant" || message.role === "system") {
163
+ return message;
164
+ }
165
+ const content = message.content.map((part) => {
166
+ if (typeof part === "string") {
167
+ return part;
168
+ } else if ("text" in part) {
169
+ return part.text;
170
+ } else if ("image" in part && typeof part.image === "object") {
171
+ const attributes = Object.entries(part.image).map(([key, value]) => {
172
+ const valueToUse = key === "data" && value != null ? value.slice(0, 22) + "..." : value;
173
+ return `${key}=${JSON.stringify(valueToUse)}`;
174
+ }).join(" ");
175
+ return `<ImagePart ${attributes} />`;
176
+ }
177
+ throw new Error("Invalid ImagePart param");
178
+ }).join("\n\n");
179
+ return {
180
+ role: message.role,
181
+ content
182
+ };
183
+ };
184
+ var UserChatMessageBuilder = class {
185
+ constructor(useBase64) {
186
+ this.useBase64 = useBase64;
187
+ }
188
+ content = [];
189
+ text(text) {
190
+ this.content.push(text);
191
+ return this;
192
+ }
193
+ image(props) {
194
+ this.content.push(props);
195
+ return this;
196
+ }
197
+ async build() {
198
+ const promises = this.content.map(
199
+ async (part) => {
200
+ if (typeof part === "string") {
201
+ return {
202
+ type: "text",
203
+ text: part
204
+ };
205
+ } else {
206
+ const image = await processImageMessageProps(part, this.useBase64);
207
+ return {
208
+ type: "image",
209
+ image
210
+ };
211
+ }
212
+ }
213
+ );
214
+ return {
215
+ role: "user",
216
+ content: await Promise.all(promises)
217
+ };
218
+ }
219
+ };
220
+ var userChatMessageBuilder = (opts) => {
221
+ return new UserChatMessageBuilder(opts.useBase64);
222
+ };
223
+
224
+ // src/chat/components.ts
81
225
  var SystemMessage = (props) => {
82
226
  return props.children;
83
227
  };
@@ -87,24 +231,22 @@ var UserMessage = (props) => {
87
231
  var AssistantMessage = (props) => {
88
232
  return props.children;
89
233
  };
90
- var computeUsage = (messages) => {
91
- const prompt = messages.filter((m) => m.role === "user" || m.role === "system").reduce((acc, m) => acc + m.tokens, 0);
92
- const completion = messages.filter((m) => m.role === "assistant").reduce((acc, m) => acc + m.tokens, 0);
234
+
235
+ // src/chat/tokenizer.ts
236
+ var computeUsage = (messages, tokenizer2) => {
237
+ const promptMessages = [...messages];
238
+ let assistantMessage;
239
+ if (promptMessages[promptMessages.length - 1].role === "assistant") {
240
+ assistantMessage = promptMessages.pop();
241
+ }
242
+ const prompt = promptMessages.reduce((acc, m) => acc + tokenizer2(m), 0);
243
+ const completion = assistantMessage ? tokenizer2(assistantMessage) : 0;
93
244
  return {
94
245
  prompt,
95
246
  completion,
96
247
  total: prompt + completion
97
248
  };
98
249
  };
99
- var ChatCompletionError = class extends Error {
100
- constructor(message, chatCompletionRequest, status, shouldRetry4 = false) {
101
- super(message);
102
- this.chatCompletionRequest = chatCompletionRequest;
103
- this.status = status;
104
- this.shouldRetry = shouldRetry4;
105
- }
106
- name = "ChatCompletionError";
107
- };
108
250
 
109
251
  // src/createElement.ts
110
252
  function createAIElement(tag, props, ...children) {
@@ -1029,7 +1171,7 @@ var attachedContextSymbol = Symbol("AI.attachedContext");
1029
1171
 
1030
1172
  // src/xml.ts
1031
1173
  var import_fast_xml_parser = require("fast-xml-parser");
1032
- var XmlNode = class {
1174
+ var XmlNode = class _XmlNode {
1033
1175
  constructor(parent, nodeName, attributes, value, childNodes) {
1034
1176
  this.parent = parent;
1035
1177
  this.nodeName = nodeName;
@@ -1041,6 +1183,15 @@ var XmlNode = class {
1041
1183
  childNodes.forEach((n) => n.parent = this);
1042
1184
  }
1043
1185
  }
1186
+ clone() {
1187
+ return new _XmlNode(
1188
+ this.parent,
1189
+ this.nodeName,
1190
+ this.attributes,
1191
+ this.value,
1192
+ this.childNodes
1193
+ );
1194
+ }
1044
1195
  toObject() {
1045
1196
  if (this.value) {
1046
1197
  return {
@@ -1736,57 +1887,17 @@ var tokenizer = {
1736
1887
  encode: (text) => cl100kTokenizer.encode(text),
1737
1888
  decode: (tokens) => cl100kTokenizer.decode(tokens)
1738
1889
  };
1739
- function tokenLimitForChatModel(model) {
1740
- const TOKENS_CONSUMED_BY_REPLY_PREFIX = 3;
1741
- switch (model) {
1742
- case "gpt-4":
1743
- case "gpt-4-0314":
1744
- case "gpt-4-0613":
1745
- return 8192 - TOKENS_CONSUMED_BY_REPLY_PREFIX;
1746
- case "gpt-4-32k":
1747
- case "gpt-4-32k-0314":
1748
- case "gpt-4-32k-0613":
1749
- return 32768 - TOKENS_CONSUMED_BY_REPLY_PREFIX;
1750
- case "gpt-4o":
1751
- case "gpt-4o-2024-05-13":
1752
- case "gpt-4-turbo-2024-04-09":
1753
- case "gpt-4-turbo":
1754
- case "gpt-4-1106-preview":
1755
- case "gpt-4-0125-preview":
1756
- return 128e3 - TOKENS_CONSUMED_BY_REPLY_PREFIX;
1757
- case "gpt-3.5-turbo":
1758
- case "gpt-3.5-turbo-0301":
1759
- case "gpt-3.5-turbo-0613":
1760
- return 4096 - TOKENS_CONSUMED_BY_REPLY_PREFIX;
1761
- case "gpt-3.5-turbo-16k":
1762
- case "gpt-3.5-turbo-16k-0613":
1763
- case "gpt-3.5-turbo-1106":
1764
- case "gpt-3.5-turbo-0125":
1765
- return 16384 - TOKENS_CONSUMED_BY_REPLY_PREFIX;
1766
- default: {
1767
- const _ = model;
1768
- return void 0;
1769
- }
1770
- }
1771
- }
1772
- function tokenCountForOpenAIMessage(message) {
1773
- const TOKENS_PER_MESSAGE = 3;
1774
- switch (message.role) {
1775
- case "assistant":
1776
- case "system":
1777
- case "user":
1778
- return (
1779
- // NOTE: this function should only be called for non vision requests,
1780
- // so message.content will be a string and not ChatCompletionContentPart[]
1781
- TOKENS_PER_MESSAGE + tokenizer.encode(message.content).length
1782
- );
1783
- }
1784
- }
1785
- function tokenCountForOpenAIVisionMessage(message) {
1786
- const TOKENS_PER_MESSAGE = 3;
1787
- const textCost = (content) => {
1788
- return TOKENS_PER_MESSAGE + tokenizer.encode(content).length;
1789
- };
1890
+ var TOKENS_PER_MESSAGE = 3;
1891
+ var textCost = (content) => {
1892
+ return TOKENS_PER_MESSAGE + tokenizer.encode(content).length;
1893
+ };
1894
+ var COST_PER_LOW = 85;
1895
+ var COST_PER_512x512 = 170;
1896
+ var imageCost = (w, h) => {
1897
+ const area = w * h;
1898
+ return Math.ceil(area / (512 * 512)) * COST_PER_512x512 + COST_PER_LOW;
1899
+ };
1900
+ var openaiTokenizer = (message) => {
1790
1901
  switch (message.role) {
1791
1902
  case "assistant":
1792
1903
  case "system":
@@ -1799,34 +1910,86 @@ function tokenCountForOpenAIVisionMessage(message) {
1799
1910
  if (part.type === "text") {
1800
1911
  return acc + textCost(part.text);
1801
1912
  } else {
1802
- if (!part.image_url.detail || part.image_url.detail === "low") {
1913
+ if (part.image.detail === "low") {
1803
1914
  return acc + 85;
1804
- } else {
1805
- return acc + (170 * 4 + 85);
1806
1915
  }
1916
+ if (part.image.dimensions) {
1917
+ return acc + imageCost(
1918
+ part.image.dimensions.width,
1919
+ part.image.dimensions.height
1920
+ );
1921
+ }
1922
+ return acc + imageCost(1024, 1024);
1807
1923
  }
1808
1924
  }, 0);
1809
1925
  }
1810
- }
1926
+ };
1811
1927
 
1812
- // src/lib/openai/utils.ts
1813
- var renderChatMessageContent = (content) => {
1814
- if (content == null) {
1815
- return "";
1928
+ // src/chat/buildMessages.ts
1929
+ async function toXml(ctx, children) {
1930
+ const childrenXml = await ctx.render(children, {
1931
+ preserveTags: true,
1932
+ renderedProps: IMAGE_RENDERED_PROPS
1933
+ });
1934
+ const topLevelTags = ["UserMessage", "AssistantMessage", "SystemMessage"];
1935
+ const chatMessageTags = [...topLevelTags, "ImagePart"];
1936
+ const parsed = parseXml(childrenXml).collapse(chatMessageTags);
1937
+ const topLevelValid = parsed.childNodes.every(
1938
+ (node) => topLevelTags.includes(node.nodeName)
1939
+ );
1940
+ if (!topLevelValid) {
1941
+ throw new Error("Invalid top level chat message tags");
1816
1942
  }
1817
- if (typeof content === "string") {
1818
- return content;
1943
+ return parsed.childNodes;
1944
+ }
1945
+ async function iterateChatMessageXml(nodes, visitFn) {
1946
+ const promises = [];
1947
+ for (const node of nodes) {
1948
+ if (node.nodeName === "UserMessage") {
1949
+ const newNode = node.clone();
1950
+ newNode.childNodes = collapseTextNodes(node.childNodes);
1951
+ promises.push(visitFn(newNode));
1952
+ } else {
1953
+ promises.push(visitFn(node));
1954
+ }
1819
1955
  }
1820
- return content.map((part) => {
1821
- if (part.type === "text") {
1822
- return part.text;
1823
- } else if (part.type === "image_url") {
1824
- let imageUrl = part.image_url.url.startsWith("data:image") ? part.image_url.url.slice(0, 22) + "..." : part.image_url.url;
1825
- return `<ContentTypeImage url="${imageUrl}" detail="${part.image_url.detail || "auto"}" />`;
1956
+ return Promise.all(promises);
1957
+ }
1958
+ async function buildChatMessages(ctx, children, opts) {
1959
+ const nodes = await toXml(ctx, children);
1960
+ const handleUserMessage = async (node) => {
1961
+ const childNodes = node.childNodes;
1962
+ const builder = userChatMessageBuilder({ useBase64: opts.useBase64Images });
1963
+ for (const n of childNodes) {
1964
+ if (n.nodeName === "#text") {
1965
+ builder.text(n.value);
1966
+ } else if (n.nodeName === "ImagePart") {
1967
+ builder.image(n.attributes);
1968
+ } else {
1969
+ throw new Error("Invalid User ChildNode, expecting Text or ImagePart");
1970
+ }
1826
1971
  }
1827
- throw new Error("Invalid ChatCompletionContentPart type");
1828
- }).join("\n\n");
1829
- };
1972
+ return builder.build();
1973
+ };
1974
+ return iterateChatMessageXml(nodes, async (node) => {
1975
+ switch (node.nodeName) {
1976
+ case "UserMessage":
1977
+ return handleUserMessage(node);
1978
+ case "SystemMessage":
1979
+ return {
1980
+ role: "system",
1981
+ content: node.textContent
1982
+ };
1983
+ case "AssistantMessage":
1984
+ return {
1985
+ role: "assistant",
1986
+ content: node.textContent
1987
+ };
1988
+ default:
1989
+ throw new Error("Invalid top level chat message tags");
1990
+ }
1991
+ });
1992
+ }
1830
1993
 
1831
1994
  // src/utils.ts
1832
1995
  function getEnvVar(name, shouldThrow = true) {
@@ -1858,35 +2021,48 @@ var OpenAIClientContext = createContext(() => {
1858
2021
  };
1859
2022
  return defaultClient;
1860
2023
  });
1861
- function buildOpenAIMessages(childrenXml) {
1862
- const messages = [];
1863
- const chatMessageTags = ["UserMessage", "AssistantMessage", "SystemMessage"];
1864
- const parsed = parseXml(childrenXml).collapse(chatMessageTags);
1865
- const topLevelValid = parsed.childNodes.every(
1866
- (node) => chatMessageTags.includes(node.nodeName)
1867
- );
1868
- if (!topLevelValid) {
1869
- throw new Error("Invalid top level chat message tags");
1870
- }
1871
- for (const node of parsed.childNodes) {
1872
- if (node.nodeName === "UserMessage") {
1873
- messages.push({
1874
- content: node.textContent,
1875
- role: "user"
1876
- });
1877
- } else if (node.nodeName === "AssistantMessage") {
1878
- messages.push({
1879
- content: node.textContent,
1880
- role: "assistant"
1881
- });
1882
- } else if (node.nodeName === "SystemMessage") {
1883
- messages.push({
1884
- content: node.textContent,
1885
- role: "system"
2024
+ function buildOpenAIMessages(chatMessages) {
2025
+ return chatMessages.map(({ role, content }) => {
2026
+ if (role === "system" || role === "assistant") {
2027
+ return {
2028
+ role,
2029
+ content
2030
+ };
2031
+ }
2032
+ if (role === "user") {
2033
+ if (content.length === 1 && content[0].type === "text") {
2034
+ return {
2035
+ role,
2036
+ content: content[0].text
2037
+ };
2038
+ }
2039
+ const c = content.map((part) => {
2040
+ if (part.type === "text") {
2041
+ return {
2042
+ type: "text",
2043
+ text: part.text
2044
+ };
2045
+ }
2046
+ if (part.type === "image") {
2047
+ const url = part.image.url || part.image.data;
2048
+ return {
2049
+ type: "image_url",
2050
+ image_url: {
2051
+ url,
2052
+ // default to auto
2053
+ detail: part.image.detail || "auto"
2054
+ }
2055
+ };
2056
+ }
2057
+ throw new Error("Invalid part");
1886
2058
  });
2059
+ return {
2060
+ role: "user",
2061
+ content: c
2062
+ };
1887
2063
  }
1888
- }
1889
- return messages;
2064
+ throw new Error("Invalid role");
2065
+ });
1890
2066
  }
1891
2067
  var shouldRetry = (error) => {
1892
2068
  return error instanceof ChatCompletionError && error.shouldRetry;
@@ -1902,26 +2078,20 @@ function OpenAIChatCompletion(props, ctx) {
1902
2078
  }
1903
2079
  );
1904
2080
  }
1905
- async function* OpenAIChatCompletionInner(props, { logger, render, tracer, getContext }) {
2081
+ async function* OpenAIChatCompletionInner(props, ctx) {
1906
2082
  const startTime = performance.now();
2083
+ const { logger, tracer, getContext } = ctx;
1907
2084
  const retryCount = getContext(RetryCountContext);
1908
2085
  const span = tracer.getActiveSpan();
1909
2086
  const { client, provider, providerRegion, costFn } = getContext(OpenAIClientContext)();
1910
2087
  if (!client) {
1911
2088
  throw new Error("[OpenAI] must supply OpenAI model via context");
1912
2089
  }
1913
- const openAIMessages = buildOpenAIMessages(
1914
- await render(props.children, {
1915
- preserveTags: true
1916
- })
1917
- );
1918
- const renderedMessages = openAIMessages.map((message) => {
1919
- return {
1920
- role: message.role,
1921
- content: renderChatMessageContent(message.content),
1922
- tokens: tokenCountForOpenAIMessage(message)
1923
- };
2090
+ const chatMessages = await buildChatMessages(ctx, props.children, {
2091
+ useBase64Images: false
1924
2092
  });
2093
+ const openAIMessages = buildOpenAIMessages(chatMessages);
2094
+ const inputMessages = chatMessages.map((m) => toDebugMessage(m));
1925
2095
  const chatCompletionRequest = {
1926
2096
  model: props.model,
1927
2097
  max_tokens: props.maxTokens,
@@ -1933,13 +2103,16 @@ async function* OpenAIChatCompletionInner(props, { logger, render, tracer, getCo
1933
2103
  } : void 0,
1934
2104
  stream: true
1935
2105
  };
2106
+ const chatCompletionRequestToLog = cleanChatCompletionRequest(
2107
+ chatCompletionRequest
2108
+ );
1936
2109
  const logRequestData = {
1937
2110
  startTime,
1938
2111
  model: props.model,
1939
2112
  provider,
1940
2113
  providerRegion,
1941
- inputMessages: renderedMessages,
1942
- request: chatCompletionRequest
2114
+ inputMessages,
2115
+ request: chatCompletionRequestToLog
1943
2116
  };
1944
2117
  logger.chatCompletionRequest("openai", logRequestData);
1945
2118
  span.setAttributes({
@@ -1947,8 +2120,8 @@ async function* OpenAIChatCompletionInner(props, { logger, render, tracer, getCo
1947
2120
  provider,
1948
2121
  providerRegion,
1949
2122
  requestType: "openai",
1950
- chatCompletionRequest,
1951
- inputMessages: renderedMessages,
2123
+ chatCompletionRequest: chatCompletionRequestToLog,
2124
+ inputMessages,
1952
2125
  retryCount
1953
2126
  });
1954
2127
  let chatResponse;
@@ -1990,13 +2163,12 @@ async function* OpenAIChatCompletionInner(props, { logger, render, tracer, getCo
1990
2163
  }
1991
2164
  const outputMessage = {
1992
2165
  role: "assistant",
1993
- content,
1994
- tokens: tokenCountForOpenAIMessage({
1995
- role: "assistant",
1996
- content
1997
- })
2166
+ content
1998
2167
  };
1999
- const tokensUsed = computeUsage([...renderedMessages, outputMessage]);
2168
+ const tokensUsed = computeUsage(
2169
+ [...chatMessages, outputMessage],
2170
+ openaiTokenizer
2171
+ );
2000
2172
  const cost = costFn?.(props.model, tokensUsed) ?? void 0;
2001
2173
  const responseData = {
2002
2174
  ...logRequestData,
@@ -2013,286 +2185,73 @@ async function* OpenAIChatCompletionInner(props, { logger, render, tracer, getCo
2013
2185
  cost
2014
2186
  });
2015
2187
  }
2188
+ function cleanChatCompletionRequest(chatCompletionRequest) {
2189
+ const { messages, ...rest } = chatCompletionRequest;
2190
+ return {
2191
+ ...rest,
2192
+ messages: messages.map((message) => {
2193
+ if (message.role !== "user") {
2194
+ return message;
2195
+ }
2196
+ if (typeof message.content === "string") {
2197
+ return message;
2198
+ }
2199
+ return {
2200
+ ...message,
2201
+ content: message.content.map((part) => {
2202
+ if (typeof part === "string") {
2203
+ return part;
2204
+ } else if (part.type === "text") {
2205
+ return part;
2206
+ } else if (part.type === "image_url") {
2207
+ const url = part.image_url.url;
2208
+ const cleanedUrl = url.startsWith("http") ? url : url.slice(0, 22) + "...";
2209
+ return {
2210
+ ...part,
2211
+ image_url: {
2212
+ ...part.image_url,
2213
+ url: cleanedUrl
2214
+ }
2215
+ };
2216
+ }
2217
+ return part;
2218
+ })
2219
+ };
2220
+ })
2221
+ };
2222
+ }
2016
2223
 
2017
- // src/lib/openai/OpenAIVision.tsx
2224
+ // src/lib/openai/index.ts
2018
2225
  var import_openai3 = require("openai");
2019
- var DEFAULT_MODEL = "gpt-4-vision-preview";
2020
- var ContentTypeImage = (_props) => {
2021
- return null;
2226
+
2227
+ // src/lib/anthropic/Anthropic.tsx
2228
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"));
2229
+
2230
+ // src/lib/anthropic/tokenizer.ts
2231
+ var import_tokenizer4 = require("@anthropic-ai/tokenizer");
2232
+ var DEFAULT_IMAGE_TOKEN_COST = 1334;
2233
+ var imageTokens = (w, h) => {
2234
+ return Math.ceil(w * h / 750);
2022
2235
  };
2023
- function buildOpenAIVisionChatMessages(childrenXml) {
2024
- const messages = [];
2025
- const chatMessageTags = [
2026
- "UserMessage",
2027
- "AssistantMessage",
2028
- "SystemMessage",
2029
- "ContentTypeImage"
2030
- ];
2031
- const parsed = parseXml(childrenXml).collapse(chatMessageTags);
2032
- const topLevelValid = parsed.childNodes.every(
2033
- (node) => chatMessageTags.includes(node.nodeName)
2034
- );
2035
- if (!topLevelValid) {
2036
- throw new Error("Invalid top level chat message tags");
2236
+ var anthropicTokenizer = (message) => {
2237
+ if (message.role === "system") {
2238
+ return (0, import_tokenizer4.countTokens)(message.content);
2037
2239
  }
2038
- const dimensions = /* @__PURE__ */ new WeakMap();
2039
- for (const node of parsed.childNodes) {
2040
- if (node.nodeName === "UserMessage") {
2041
- const parts = node.childNodes.map((n) => {
2042
- if (n.nodeName === "#text") {
2043
- return {
2044
- type: "text",
2045
- text: n.value
2046
- };
2047
- } else if (n.nodeName === "ContentTypeImage") {
2048
- const imagePart = {
2049
- type: "image_url",
2050
- image_url: {
2051
- url: n.attributes.url,
2052
- detail: n.attributes.detail || "auto"
2053
- }
2054
- };
2055
- dimensions.set(imagePart, n.attributes.dimensions);
2056
- return imagePart;
2057
- }
2058
- throw new Error(
2059
- "Invalid ChatCompletionContentPart, expecting text or ContentTypeImage"
2060
- );
2061
- });
2062
- messages.push({
2063
- content: parts,
2064
- role: "user"
2065
- });
2066
- } else if (node.nodeName === "AssistantMessage") {
2067
- messages.push({
2068
- content: node.textContent,
2069
- role: "assistant"
2070
- });
2071
- } else if (node.nodeName === "SystemMessage") {
2072
- messages.push({
2073
- content: node.textContent,
2074
- role: "system"
2075
- });
2076
- }
2240
+ if (message.role === "assistant") {
2241
+ return (0, import_tokenizer4.countTokens)(message.content);
2077
2242
  }
2078
- return { messages, dimensions };
2079
- }
2080
- var shouldRetry2 = (error) => {
2081
- return error instanceof ChatCompletionError && error.shouldRetry;
2082
- };
2083
- function OpenAIVisionChatCompletion(props, ctx) {
2084
- const defaultMaxRetries = ctx.getContext(DefaultMaxRetriesContext);
2085
- return /* @__PURE__ */ jsx(
2086
- Retry,
2087
- {
2088
- maxRetries: props.maxRetries || defaultMaxRetries,
2089
- shouldRetry: shouldRetry2,
2090
- children: /* @__PURE__ */ jsx(Trace, { name: "ai.chatCompletion", children: /* @__PURE__ */ jsx(OpenAIVisionChatCompletionInner, { ...props }) })
2243
+ return message.content.reduce((carry, item) => {
2244
+ let tokens = 0;
2245
+ if (item.type === "text") {
2246
+ tokens = (0, import_tokenizer4.countTokens)(item.text);
2247
+ } else {
2248
+ tokens = item.image.dimensions ? imageTokens(item.image.dimensions.width, item.image.dimensions.height) : DEFAULT_IMAGE_TOKEN_COST;
2091
2249
  }
2092
- );
2093
- }
2094
- async function* OpenAIVisionChatCompletionInner(props, { logger, render, tracer, getContext }) {
2095
- const startTime = performance.now();
2096
- const model = props.model || DEFAULT_MODEL;
2097
- const retryCount = getContext(RetryCountContext);
2098
- const span = tracer.getActiveSpan();
2099
- const { client, provider, providerRegion, costFn } = getContext(OpenAIClientContext)();
2100
- if (!client) {
2101
- throw new Error("[OpenAI] must supply OpenAI model via context");
2102
- }
2103
- const { messages: openAIMessages, dimensions } = buildOpenAIVisionChatMessages(
2104
- await render(props.children, {
2105
- preserveTags: true,
2106
- renderedProps: {
2107
- ContentTypeImage: {
2108
- url: true,
2109
- dimensions: true,
2110
- detail: true
2111
- }
2112
- }
2113
- })
2114
- );
2115
- const renderedMessages = openAIMessages.map((message) => {
2116
- if (message.role === "user") {
2117
- if (typeof message.content === "string") {
2118
- return {
2119
- role: message.role,
2120
- content: message.content,
2121
- tokens: tokenCountForOpenAIMessage(message)
2122
- };
2123
- }
2124
- const BASE_COST = 85;
2125
- const tokens = message.content.reduce((acc, part) => {
2126
- if (part.type === "text") {
2127
- return acc + tokenCountForOpenAIMessage({
2128
- role: message.role,
2129
- content: part.text
2130
- });
2131
- }
2132
- const detail = part.image_url.detail || "auto";
2133
- if (detail === "low") {
2134
- return acc + BASE_COST;
2135
- } else if (detail === "high") {
2136
- const dim = dimensions.get(part);
2137
- if (!dim) {
2138
- return acc + (170 * 4 + BASE_COST);
2139
- }
2140
- const area = dim.width * dim.height;
2141
- const num512Images = area / (512 * 512);
2142
- const highCost = num512Images * 170;
2143
- return acc + highCost + BASE_COST;
2144
- } else {
2145
- return acc + (170 * 4 + BASE_COST);
2146
- }
2147
- }, 0);
2148
- return {
2149
- role: message.role,
2150
- content: renderChatMessageContent(message.content),
2151
- tokens
2152
- };
2153
- }
2154
- return {
2155
- role: message.role,
2156
- content: renderChatMessageContent(message.content),
2157
- tokens: tokenCountForOpenAIMessage(message)
2158
- };
2159
- });
2160
- const chatCompletionRequest = {
2161
- model,
2162
- max_tokens: props.maxTokens,
2163
- temperature: props.temperature,
2164
- stop: props.stop,
2165
- messages: openAIMessages,
2166
- stream: true
2167
- };
2168
- const chatCompletionRequestToLog = cleanChatCompletionRequest(
2169
- chatCompletionRequest
2170
- );
2171
- const logRequestData = {
2172
- startTime,
2173
- model,
2174
- provider,
2175
- providerRegion,
2176
- inputMessages: renderedMessages,
2177
- request: chatCompletionRequestToLog
2178
- };
2179
- logger.chatCompletionRequest("openai", logRequestData);
2180
- span.setAttributes({
2181
- model: props.model,
2182
- provider,
2183
- providerRegion,
2184
- requestType: "openai",
2185
- chatCompletionRequest: chatCompletionRequestToLog,
2186
- inputMessages: renderedMessages,
2187
- retryCount
2188
- });
2189
- let chatResponse;
2190
- try {
2191
- chatResponse = await client.chat.completions.create(chatCompletionRequest);
2192
- } catch (ex) {
2193
- const retry = shouldRetryOpenAI(ex);
2194
- if (ex instanceof import_openai3.OpenAI.APIError) {
2195
- throw new ChatCompletionError(
2196
- `OpenAIClient.APIError: ${ex.message}`,
2197
- logRequestData,
2198
- ex.status,
2199
- retry
2200
- );
2201
- } else if (ex instanceof Error) {
2202
- throw new ChatCompletionError(
2203
- ex.message,
2204
- logRequestData,
2205
- void 0,
2206
- retry
2207
- );
2208
- }
2209
- throw ex;
2210
- }
2211
- let finishReason = void 0;
2212
- let content = "";
2213
- for await (const message of chatResponse) {
2214
- if (!message.choices || !message.choices[0]) {
2215
- continue;
2216
- }
2217
- const delta = message.choices[0].delta;
2218
- if (message.choices[0].finish_reason) {
2219
- finishReason = message.choices[0].finish_reason;
2220
- }
2221
- if (delta.content) {
2222
- content += delta.content;
2223
- yield delta.content;
2224
- }
2225
- }
2226
- const outputMessage = {
2227
- role: "assistant",
2228
- content,
2229
- tokens: tokenCountForOpenAIMessage({
2230
- role: "assistant",
2231
- content
2232
- })
2233
- };
2234
- const tokensUsed = computeUsage([...renderedMessages, outputMessage]);
2235
- const cost = costFn?.(model, tokensUsed) ?? void 0;
2236
- const responseData = {
2237
- ...logRequestData,
2238
- finishReason,
2239
- latency: performance.now() - startTime,
2240
- outputMessage,
2241
- tokensUsed
2242
- };
2243
- logger.chatCompletionResponse("openai", responseData);
2244
- span.setAttributes({
2245
- tokensUsed,
2246
- output: content,
2247
- cost,
2248
- finishReason
2249
- });
2250
- }
2251
- function cleanChatCompletionRequest(chatCompletionRequest) {
2252
- const { messages, ...rest } = chatCompletionRequest;
2253
- return {
2254
- ...rest,
2255
- messages: messages.map((message) => {
2256
- if (message.role !== "user") {
2257
- return message;
2258
- }
2259
- if (typeof message.content === "string") {
2260
- return message;
2261
- }
2262
- return {
2263
- ...message,
2264
- content: message.content.map((part) => {
2265
- if (part.type === "text") {
2266
- return part;
2267
- }
2268
- if (part.image_url.url.startsWith("data:image")) {
2269
- return {
2270
- type: "image_url",
2271
- image_url: {
2272
- url: part.image_url.url.slice(0, 22) + "...",
2273
- detail: part.image_url.detail
2274
- }
2275
- };
2276
- }
2277
- return {
2278
- type: "image_url",
2279
- image_url: {
2280
- url: part.image_url.url,
2281
- detail: part.image_url.detail
2282
- }
2283
- };
2284
- })
2285
- };
2286
- })
2287
- };
2288
- }
2289
-
2290
- // src/lib/openai/index.ts
2291
- var import_openai4 = require("openai");
2250
+ return carry + tokens;
2251
+ }, 0);
2252
+ };
2292
2253
 
2293
2254
  // src/lib/anthropic/Anthropic.tsx
2294
- var import_sdk = __toESM(require("@anthropic-ai/sdk"));
2295
- var import_tokenizer3 = require("@anthropic-ai/tokenizer");
2296
2255
  var defaultClient2 = null;
2297
2256
  var AnthropicClientContext = createContext(() => {
2298
2257
  if (defaultClient2) {
@@ -2308,71 +2267,69 @@ var AnthropicClientContext = createContext(() => {
2308
2267
  return defaultClient2;
2309
2268
  });
2310
2269
  var defaultMaxTokens = 4096;
2311
- function buildAnthropicMessages(childrenXml) {
2270
+ var buildAnthropicMessages = (chatMesssages) => {
2312
2271
  let system = "";
2313
2272
  const messages = [];
2314
- const chatMessageTags = [
2315
- "UserMessage",
2316
- "AssistantMessage",
2317
- "SystemMessage",
2318
- "ClaudeImageBlockParam"
2319
- ];
2320
- const parsed = parseXml(childrenXml).collapse(chatMessageTags);
2321
- const topLevelValid = parsed.childNodes.every(
2322
- (node) => chatMessageTags.includes(node.nodeName)
2323
- );
2324
- if (!topLevelValid) {
2325
- throw new Error("Invalid top level chat message tags");
2326
- }
2327
- for (const node of parsed.childNodes) {
2328
- if (node.nodeName === "UserMessage") {
2329
- const childNodes = collapseTextNodes(node.childNodes);
2330
- if (childNodes.length === 1 && childNodes[0].nodeName === "#text") {
2273
+ chatMesssages.forEach(({ role, content }) => {
2274
+ if (role === "system") {
2275
+ system = content;
2276
+ return;
2277
+ }
2278
+ if (role === "user") {
2279
+ const userContent = content;
2280
+ if (userContent.length === 1 && userContent[0].type === "text") {
2331
2281
  messages.push({
2332
- content: childNodes[0].value,
2333
- role: "user"
2282
+ role,
2283
+ content: userContent[0].text
2334
2284
  });
2335
- continue;
2285
+ return;
2336
2286
  }
2337
- const parts = childNodes.map((n) => {
2338
- if (n.nodeName === "#text") {
2339
- return {
2340
- type: "text",
2341
- text: n.value
2342
- };
2343
- } else if (n.nodeName === "ClaudeImageBlockParam") {
2287
+ const c = userContent.map((part) => {
2288
+ if (part.type === "text") {
2289
+ return part;
2290
+ } else if (part.type === "image") {
2344
2291
  const imagePart = {
2345
2292
  type: "image",
2346
2293
  source: {
2347
2294
  type: "base64",
2348
- media_type: n.attributes.mediaType,
2349
- data: n.attributes.data
2295
+ // NOTE: these `!` rely on the fact that we used `useBase64: true` in `userMessageBuilder``
2296
+ media_type: part.image.mediaType,
2297
+ data: part.image.data
2350
2298
  }
2351
2299
  };
2352
2300
  return imagePart;
2353
2301
  }
2354
- throw new Error(
2355
- "Invalid ChatCompletionContentPart, expecting text or ContentTypeImage"
2356
- );
2357
- }).filter((n) => n.type !== "text" || n.text.trim().length > 0);
2302
+ throw new Error("Invalid part");
2303
+ }).filter(
2304
+ (a) => (
2305
+ // allow image messages
2306
+ a.type === "image" || a.type === "text" && a.text.trim().length > 0
2307
+ )
2308
+ );
2358
2309
  messages.push({
2359
- content: parts,
2360
- role: "user"
2310
+ role,
2311
+ content: c
2361
2312
  });
2362
- } else if (node.nodeName === "AssistantMessage") {
2313
+ return;
2314
+ }
2315
+ if (role === "assistant") {
2363
2316
  messages.push({
2364
- content: node.textContent,
2365
- role: "assistant"
2317
+ role,
2318
+ content
2366
2319
  });
2367
- } else if (node.nodeName === "SystemMessage") {
2368
- system = node.textContent;
2320
+ return;
2369
2321
  }
2370
- }
2371
- return { messages, system };
2372
- }
2373
- var shouldRetry3 = (error) => {
2322
+ throw new Error(`Invalid role: ${role}`);
2323
+ });
2324
+ return {
2325
+ system,
2326
+ messages
2327
+ };
2328
+ };
2329
+ var shouldRetry2 = (error) => {
2374
2330
  return error instanceof ChatCompletionError && error.shouldRetry;
2375
2331
  };
2332
+ var shouldRetryFromStatus = (status) => Boolean(status && [424, 429, 500].includes(status));
2376
2333
  var RE_INTERNAL_SERVER_MESSASGE = /The system encountered an unexpected error during processing/i;
2377
2334
  var RE_RATE_LIMIT_MESSAGE = /Too many requests, please wait before trying again/;
2378
2335
  var extractStatusFromError = (error) => {
@@ -2406,13 +2363,14 @@ function AnthropicChatCompletion(props, ctx) {
2406
2363
  Retry,
2407
2364
  {
2408
2365
  maxRetries: props.maxRetries || defaultMaxRetries,
2409
- shouldRetry: shouldRetry3,
2366
+ shouldRetry: shouldRetry2,
2410
2367
  children: /* @__PURE__ */ jsx(Trace, { name: "ai.chatCompletion", children: /* @__PURE__ */ jsx(AnthropicChatCompletionInner, { ...props }) })
2411
2368
  }
2412
2369
  );
2413
2370
  }
2414
- async function* AnthropicChatCompletionInner(props, { render, logger, tracer, getContext }) {
2371
+ async function* AnthropicChatCompletionInner(props, ctx) {
2415
2372
  const startTime = performance.now();
2373
+ const { logger, tracer, getContext } = ctx;
2416
2374
  const retryCount = getContext(RetryCountContext);
2417
2375
  const span = tracer.getActiveSpan();
2418
2376
  const { client, provider, providerRegion, costFn } = getContext(
@@ -2423,18 +2381,11 @@ async function* AnthropicChatCompletionInner(props, { render, logger, tracer, ge
2423
2381
  "[AnthropicChatCompletion] must supply AnthropicClient via context"
2424
2382
  );
2425
2383
  }
2426
- const { system, messages } = buildAnthropicMessages(
2427
- await render(props.children, {
2428
- preserveTags: true,
2429
- renderedProps: {
2430
- ClaudeImageBlockParam: {
2431
- data: true,
2432
- mediaType: true
2433
- }
2434
- }
2435
- })
2436
- );
2437
- const inputMessages = getRenderedMessages(system, messages);
2384
+ const chatMessages = await buildChatMessages(ctx, props.children, {
2385
+ useBase64Images: true
2386
+ });
2387
+ const { system, messages } = buildAnthropicMessages(chatMessages);
2388
+ const inputMessages = chatMessages.map((m) => toDebugMessage(m));
2438
2389
  let stopSequences;
2439
2390
  if (props.stop && typeof props.stop === "string") {
2440
2391
  stopSequences = [props.stop];
@@ -2475,25 +2426,24 @@ async function* AnthropicChatCompletionInner(props, { render, logger, tracer, ge
2475
2426
  response = client.messages.stream(anthropicCompletionRequest);
2476
2427
  } catch (err) {
2477
2428
  if (err instanceof import_sdk.default.APIError) {
2478
- const retry = "status" in err && typeof err.status === "number" && err.status !== 400;
2429
+ const status = extractStatusFromError(err);
2430
+ const retry = shouldRetryFromStatus(status);
2479
2431
  throw new ChatCompletionError(
2480
2432
  `AnthropicClient.APIError: ${err.message}`,
2481
2433
  logRequestData,
2482
- extractStatusFromError(err),
2434
+ status,
2483
2435
  retry
2484
2436
  );
2485
2437
  } else if (err instanceof Error) {
2486
- throw new ChatCompletionError(
2487
- err.message,
2488
- logRequestData,
2489
- extractStatusFromError(err)
2490
- );
2438
+ const status = extractStatusFromError(err);
2439
+ const retry = shouldRetryFromStatus(status);
2440
+ throw new ChatCompletionError(err.message, logRequestData, status, retry);
2491
2441
  }
2492
2442
  throw err;
2493
2443
  }
2494
2444
  let content = "";
2495
- let outputUsage = 0;
2496
- let inputUsage = 0;
2445
+ let outputUsage;
2446
+ let inputUsage;
2497
2447
  let finishReason = null;
2498
2448
  try {
2499
2449
  for await (const event of response) {
@@ -2512,25 +2462,18 @@ async function* AnthropicChatCompletionInner(props, { render, logger, tracer, ge
2512
2462
  }
2513
2463
  } catch (e) {
2514
2464
  const status = extractStatusFromError(e);
2515
- const retry = status === 429 || status === 500;
2465
+ const retry = shouldRetryFromStatus(status);
2516
2466
  throw new ChatCompletionError(e.message, logRequestData, status, retry);
2517
2467
  }
2518
2468
  const outputMessage = {
2519
2469
  role: "assistant",
2520
- content,
2521
- tokens: outputUsage
2470
+ content
2522
2471
  };
2523
- const userMessage = inputMessages.find(
2524
- (m) => m.role === "user" && m.tokens === 0
2525
- );
2526
- const restMessages = inputMessages.filter(
2527
- (m) => m.role !== "assistant" && m.tokens > 0
2528
- );
2529
- const restMessagesTokens = restMessages.reduce((acc, m) => acc + m.tokens, 0);
2530
- if (userMessage && inputUsage) {
2531
- userMessage.tokens = inputUsage - restMessagesTokens;
2532
- }
2533
- const tokensUsed = computeUsage([...inputMessages, outputMessage]);
2472
+ const tokensUsed = inputUsage !== void 0 && outputUsage !== void 0 ? {
2473
+ prompt: inputUsage,
2474
+ completion: outputUsage,
2475
+ total: inputUsage + outputUsage
2476
+ } : computeUsage([...chatMessages, outputMessage], anthropicTokenizer);
2534
2477
  const cost = costFn?.(props.model, tokensUsed) ?? void 0;
2535
2478
  const responseData = {
2536
2479
  ...logRequestData,
@@ -2551,47 +2494,6 @@ async function* AnthropicChatCompletionInner(props, { render, logger, tracer, ge
2551
2494
  finishReason
2552
2495
  });
2553
2496
  }
2554
- function getRenderedMessages(system, messages) {
2555
- const systemMessage = {
2556
- role: "system",
2557
- content: system,
2558
- tokens: (0, import_tokenizer3.countTokens)(system)
2559
- };
2560
- const renderedMessages = messages.map((message) => {
2561
- if (message.role === "user") {
2562
- return {
2563
- role: message.role,
2564
- content: renderChatMessageContent2(message.content),
2565
- // we keep user message tokens 0 and just let anthropic tell us
2566
- tokens: 0
2567
- };
2568
- }
2569
- return {
2570
- role: message.role,
2571
- content: renderChatMessageContent2(message.content),
2572
- tokens: (0, import_tokenizer3.countTokens)(message.content)
2573
- };
2574
- });
2575
- return [systemMessage, ...renderedMessages];
2576
- }
2577
- var renderChatMessageContent2 = (content) => {
2578
- if (content == null) {
2579
- return "";
2580
- }
2581
- if (typeof content === "string") {
2582
- return content;
2583
- }
2584
- return content.map((part) => {
2585
- if (typeof part === "string") {
2586
- return part;
2587
- } else if ("text" in part) {
2588
- return part.text;
2589
- } else if ("source" in part && typeof part.source === "object") {
2590
- return `<ImageBlockParam data="base64..." />`;
2591
- }
2592
- throw new Error("Invalid ImageBlockParam type");
2593
- }).join("\n\n");
2594
- };
2595
2497
  function cleanChatCompletionRequest2(chatCompletionRequest) {
2596
2498
  const { messages, ...rest } = chatCompletionRequest;
2597
2499
  return {
@@ -2626,62 +2528,284 @@ function cleanChatCompletionRequest2(chatCompletionRequest) {
2626
2528
  };
2627
2529
  }
2628
2530
 
2629
- // src/lib/anthropic/ClaudeImageBlock.tsx
2630
- var ClaudeImageBlockParam = (_props) => {
2631
- return null;
2531
+ // src/lib/anthropic/index.ts
2532
+ var import_sdk2 = __toESM(require("@anthropic-ai/sdk"));
2533
+
2534
+ // src/lib/google/Google.tsx
2535
+ var import_vertexai = require("@google-cloud/vertexai");
2536
+ var GoogleClientContext = createContext(() => {
2537
+ if (defaultClient3) {
2538
+ return defaultClient3;
2539
+ }
2540
+ const project = process.env.GOOGLE_PROJECT_ID || "";
2541
+ const client = new import_vertexai.VertexAI({ project });
2542
+ defaultClient3 = {
2543
+ client,
2544
+ provider: "google"
2545
+ };
2546
+ return defaultClient3;
2547
+ });
2548
+ var defaultClient3 = null;
2549
+ var buildGoogleMessages = (chatMesssages) => {
2550
+ let systemInstruction;
2551
+ const messages = [];
2552
+ chatMesssages.forEach(({ role, content }) => {
2553
+ if (role === "system") {
2554
+ systemInstruction = content;
2555
+ return;
2556
+ }
2557
+ if (role === "user") {
2558
+ const userContent = content;
2559
+ if (userContent.length === 1 && userContent[0].type === "text") {
2560
+ messages.push({
2561
+ role,
2562
+ parts: [{ text: userContent[0].text }]
2563
+ });
2564
+ return;
2565
+ }
2566
+ const c = userContent.map((part) => {
2567
+ if (part.type === "text") {
2568
+ return { text: part.text };
2569
+ } else if (part.type === "image") {
2570
+ const imagePart = {
2571
+ inlineData: {
2572
+ mimeType: part.image.mediaType,
2573
+ data: part.image.data
2574
+ }
2575
+ };
2576
+ return imagePart;
2577
+ }
2578
+ throw new Error("Invalid part");
2579
+ });
2580
+ messages.push({
2581
+ role,
2582
+ parts: c
2583
+ });
2584
+ return;
2585
+ }
2586
+ if (role === "assistant") {
2587
+ messages.push({
2588
+ role,
2589
+ parts: [{ text: content }]
2590
+ });
2591
+ return;
2592
+ }
2593
+ throw new Error(`Invalid role: ${role}`);
2594
+ });
2595
+ return {
2596
+ systemInstruction,
2597
+ messages
2598
+ };
2632
2599
  };
2633
- async function fetchImageAndConvertToBase64(url) {
2634
- const response = await fetch(url);
2635
- const contentType = response.headers.get("content-type");
2636
- const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
2637
- if (!contentType || !allowedTypes.includes(contentType)) {
2638
- throw new Error(`Unsupported media type: ${contentType}`);
2600
+ var shouldRetry3 = (error) => {
2601
+ return error instanceof ChatCompletionError && error.shouldRetry;
2602
+ };
2603
+ function GoogleChatCompletion(props, ctx) {
2604
+ const defaultMaxRetries = ctx.getContext(DefaultMaxRetriesContext);
2605
+ return /* @__PURE__ */ jsx(
2606
+ Retry,
2607
+ {
2608
+ maxRetries: props.maxRetries || defaultMaxRetries,
2609
+ shouldRetry: shouldRetry3,
2610
+ children: /* @__PURE__ */ jsx(Trace, { name: "ai.chatCompletion", children: /* @__PURE__ */ jsx(GoogleChatCompletionInner, { ...props }) })
2611
+ }
2612
+ );
2613
+ }
2614
+ async function* GoogleChatCompletionInner(props, ctx) {
2615
+ const startTime = performance.now();
2616
+ const { logger, tracer, getContext } = ctx;
2617
+ const retryCount = getContext(RetryCountContext);
2618
+ const span = tracer.getActiveSpan();
2619
+ const { client, provider, providerRegion, costFn } = getContext(GoogleClientContext)();
2620
+ if (!client) {
2621
+ throw new Error(
2622
+ "[GoogleChatCompletion] must supply GoogleClient via context"
2623
+ );
2639
2624
  }
2640
- const blob = await response.blob();
2641
- const buffer = Buffer.from(await blob.arrayBuffer());
2642
- const base64 = buffer.toString("base64");
2625
+ const chatMessages = await buildChatMessages(ctx, props.children, {
2626
+ useBase64Images: true
2627
+ });
2628
+ const { systemInstruction, messages } = buildGoogleMessages(chatMessages);
2629
+ const inputMessages = chatMessages.map((m) => toDebugMessage(m));
2630
+ let stopSequences;
2631
+ if (props.stop && typeof props.stop === "string") {
2632
+ stopSequences = [props.stop];
2633
+ } else if (Array.isArray(props.stop)) {
2634
+ stopSequences = props.stop;
2635
+ }
2636
+ const googleCompletionRequest = {
2637
+ systemInstruction,
2638
+ contents: messages,
2639
+ generationConfig: {
2640
+ maxOutputTokens: props.maxTokens,
2641
+ temperature: props.temperature,
2642
+ stopSequences
2643
+ }
2644
+ };
2645
+ const chatCompletionRequestToLog = cleanChatCompletionRequest3(
2646
+ googleCompletionRequest
2647
+ );
2648
+ const logRequestData = {
2649
+ startTime,
2650
+ model: props.model,
2651
+ provider,
2652
+ providerRegion,
2653
+ inputMessages,
2654
+ request: chatCompletionRequestToLog
2655
+ };
2656
+ logger.chatCompletionRequest("google", logRequestData);
2657
+ span.setAttributes({
2658
+ model: props.model,
2659
+ provider,
2660
+ providerRegion,
2661
+ requestType: "google",
2662
+ chatCompletionRequest: chatCompletionRequestToLog,
2663
+ inputMessages,
2664
+ retryCount
2665
+ });
2666
+ const model = client.getGenerativeModel({ model: props.model });
2667
+ let response;
2668
+ try {
2669
+ response = await model.generateContentStream(googleCompletionRequest);
2670
+ } catch (err) {
2671
+ if (err instanceof import_vertexai.GoogleGenerativeAIError) {
2672
+ throw new ChatCompletionError(
2673
+ err.message,
2674
+ logRequestData,
2675
+ void 0,
2676
+ // always retry on GoogleGenerativeAIError errors, they represent non 4xx error cases
2677
+ true
2678
+ );
2679
+ } else if (err instanceof Error) {
2680
+ const status = void 0;
2681
+ const retry = false;
2682
+ throw new ChatCompletionError(err.message, logRequestData, status, retry);
2683
+ }
2684
+ throw err;
2685
+ }
2686
+ let content = "";
2687
+ let outputUsage = 0;
2688
+ let inputUsage = 0;
2689
+ let finishReason = null;
2690
+ try {
2691
+ for await (const event of response.stream) {
2692
+ if (event.candidates) {
2693
+ const chunk = event.candidates[0].content;
2694
+ if (event.candidates[0]?.finishReason) {
2695
+ finishReason = event.candidates[0].finishReason;
2696
+ }
2697
+ if (event.usageMetadata) {
2698
+ if (event.usageMetadata.promptTokenCount) {
2699
+ inputUsage = event.usageMetadata.promptTokenCount;
2700
+ }
2701
+ if (event.usageMetadata.candidatesTokenCount) {
2702
+ outputUsage = event.usageMetadata.candidatesTokenCount;
2703
+ }
2704
+ }
2705
+ for (const block of chunk.parts) {
2706
+ if (block.text) {
2707
+ content += block.text;
2708
+ yield block.text;
2709
+ }
2710
+ }
2711
+ }
2712
+ }
2713
+ } catch (err) {
2714
+ if (err instanceof import_vertexai.GoogleGenerativeAIError) {
2715
+ throw new ChatCompletionError(
2716
+ err.message,
2717
+ logRequestData,
2718
+ void 0,
2719
+ // always retry these errors they represent non 4xx error cases
2720
+ true
2721
+ );
2722
+ } else if (err instanceof Error) {
2723
+ const status = void 0;
2724
+ const retry = false;
2725
+ throw new ChatCompletionError(err.message, logRequestData, status, retry);
2726
+ }
2727
+ throw err;
2728
+ }
2729
+ const outputMessage = {
2730
+ role: "assistant",
2731
+ content
2732
+ };
2733
+ const tokensUsed = {
2734
+ prompt: inputUsage,
2735
+ completion: outputUsage,
2736
+ total: inputUsage + outputUsage
2737
+ };
2738
+ const cost = costFn?.(props.model, tokensUsed) ?? void 0;
2739
+ const responseData = {
2740
+ ...logRequestData,
2741
+ finishReason,
2742
+ latency: performance.now() - startTime,
2743
+ inputMessages,
2744
+ outputMessage,
2745
+ tokensUsed
2746
+ };
2747
+ logger.chatCompletionResponse("google", responseData);
2748
+ span.setAttributes({
2749
+ tokensUsed,
2750
+ output: content,
2751
+ cost,
2752
+ finishReason
2753
+ });
2754
+ }
2755
+ function cleanChatCompletionRequest3(chatCompletionRequest) {
2756
+ const { contents, ...rest } = chatCompletionRequest;
2643
2757
  return {
2644
- base64,
2645
- mediaType: contentType
2758
+ ...rest,
2759
+ contents: contents.map((message) => {
2760
+ if (message.role !== "user") {
2761
+ return message;
2762
+ }
2763
+ return {
2764
+ ...message,
2765
+ parts: message.parts.map((part) => {
2766
+ if ("text" in part) {
2767
+ return part;
2768
+ } else if ("inlineData" in part) {
2769
+ return {
2770
+ inlineData: {
2771
+ ...part.inlineData,
2772
+ mimeType: part.inlineData?.mimeType || "",
2773
+ data: part.inlineData?.data?.slice(0, 22) + "..."
2774
+ }
2775
+ };
2776
+ }
2777
+ return part;
2778
+ })
2779
+ };
2780
+ })
2646
2781
  };
2647
2782
  }
2648
- var ClaudeImageBlock = async (props) => {
2649
- if ("data" in props) {
2650
- return /* @__PURE__ */ jsx(ClaudeImageBlockParam, { ...props });
2651
- }
2652
- const { url } = props;
2653
- const { base64, mediaType } = await fetchImageAndConvertToBase64(url);
2654
- return /* @__PURE__ */ jsx(ClaudeImageBlockParam, { mediaType, data: base64 });
2655
- };
2656
2783
 
2657
- // src/lib/anthropic/index.ts
2658
- var import_sdk2 = __toESM(require("@anthropic-ai/sdk"));
2659
- var import_tokenizer4 = require("@anthropic-ai/tokenizer");
2784
+ // src/lib/google/index.ts
2785
+ var import_vertexai2 = require("@google-cloud/vertexai");
2660
2786
  // Annotate the CommonJS export names for ESM import in node:
2661
2787
  0 && (module.exports = {
2662
2788
  AIFragment,
2663
2789
  AISpanProcessor,
2664
2790
  AnthropicChatCompletion,
2665
- AnthropicChatCompletionInner,
2666
2791
  AnthropicClient,
2667
2792
  AnthropicClientContext,
2668
2793
  AssistantMessage,
2669
2794
  BoundLogger,
2670
2795
  ChatCompletionError,
2671
- ClaudeImageBlock,
2672
2796
  CombinedLogger,
2673
2797
  ConsoleLogger,
2674
- ContentTypeImage,
2675
2798
  DefaultMaxRetriesContext,
2676
2799
  EnrichingSpanProcessor,
2677
2800
  Fallback,
2801
+ GoogleChatCompletion,
2802
+ GoogleClientContext,
2803
+ ImagePart,
2678
2804
  LogImplementation,
2679
2805
  NoopLogImplementation,
2680
2806
  OpenAIChatCompletion,
2681
2807
  OpenAIClient,
2682
2808
  OpenAIClientContext,
2683
- OpenAIVisionChatCompletion,
2684
- OpenAIVisionChatCompletionInner,
2685
2809
  ParseVariablesError,
2686
2810
  PromptInvalidOutputError,
2687
2811
  Retry,
@@ -2689,9 +2813,10 @@ var import_tokenizer4 = require("@anthropic-ai/tokenizer");
2689
2813
  SystemMessage,
2690
2814
  Trace,
2691
2815
  UserMessage,
2816
+ VertexAI,
2817
+ anthropicTokenizer,
2692
2818
  attachedContextSymbol,
2693
2819
  computeUsage,
2694
- countAnthropicTokens,
2695
2820
  createAIElement,
2696
2821
  createContext,
2697
2822
  createFunctionChain,
@@ -2699,9 +2824,7 @@ var import_tokenizer4 = require("@anthropic-ai/tokenizer");
2699
2824
  createRenderContext,
2700
2825
  createStreamChain,
2701
2826
  evaluatePrompt,
2702
- tokenCountForOpenAIMessage,
2703
- tokenCountForOpenAIVisionMessage,
2704
- tokenLimitForChatModel,
2705
- tokenizer,
2827
+ openaiTokenizer,
2828
+ toDebugMessage,
2706
2829
  tracing
2707
2830
  });