@chainfuse/ai-tools 0.1.1 → 0.2.0

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/README.md CHANGED
@@ -38,10 +38,10 @@ generateText({
38
38
  },
39
39
  country: 'ISO 3166-1 Alpha 2 country code',
40
40
  continent: 'two-letter code of continent',
41
- environment: 'the gateway to use',
42
- providers: {
43
- // api keys and any additional info needed for each service
44
- },
41
+ },
42
+ environment: 'the gateway to use',
43
+ providers: {
44
+ // api keys and any additional info needed for each service
45
45
  },
46
46
  },
47
47
  // ...
package/dist/base.mjs CHANGED
@@ -6,6 +6,6 @@ export class AiBase {
6
6
  this._config = config;
7
7
  }
8
8
  get config() {
9
- return this._config;
9
+ return Object.freeze(this._config);
10
10
  }
11
11
  }
@@ -1,10 +1,14 @@
1
+ import type { GoogleGenerativeAIProvider } from '@ai-sdk/google';
2
+ import type { OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
1
3
  import { AiBase } from '../base.mjs';
2
4
  import type { AiRequestConfig } from '../types.mjs';
3
- import type { AzureOpenAIProvider, CloudflareOpenAIProvider } from './types.mjs';
5
+ import type { AzureOpenAIProvider } from './types.mjs';
4
6
  export declare class AiCustomProviders extends AiBase {
5
7
  oaiOpenai(args: AiRequestConfig): Promise<import("@ai-sdk/openai").OpenAIProvider>;
6
8
  azOpenai(args: AiRequestConfig, [server, ...servers]?: import("../serverSelector/types.mjs").Server[]): Promise<AzureOpenAIProvider>;
7
9
  anthropic(args: AiRequestConfig): Promise<import("@ai-sdk/anthropic").AnthropicProvider>;
8
10
  private static workersAiIsRest;
9
- cfWorkersAi(args: AiRequestConfig): Promise<CloudflareOpenAIProvider>;
11
+ cfWorkersAi(args: AiRequestConfig): Promise<OpenAICompatibleProvider<"@cf/meta/llama-2-7b-chat-int8" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/meta/llama-2-7b-chat-fp16" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/openchat/openchat-3.5-0106" | "@cf/tiiuae/falcon-7b-instruct" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/qwen/qwen1.5-0.5b-chat" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@cf/microsoft/phi-2" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@hf/nexusflow/starling-lm-7b-beta" | "@hf/google/gemma-7b-it" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/google/gemma-2b-it-lora" | "@cf/google/gemma-7b-it-lora" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/meta/llama-3-8b-instruct" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/meta/llama-3-8b-instruct-awq" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@cf/meta/llama-3.2-3b-instruct" | "@cf/meta/llama-3.2-1b-instruct" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@cf/meta/llama-3.2-11b-vision-instruct", "@cf/meta/llama-2-7b-chat-int8" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/meta/llama-2-7b-chat-fp16" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/openchat/openchat-3.5-0106" | "@cf/tiiuae/falcon-7b-instruct" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/qwen/qwen1.5-0.5b-chat" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@cf/microsoft/phi-2" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@hf/nexusflow/starling-lm-7b-beta" | "@hf/google/gemma-7b-it" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/google/gemma-2b-it-lora" | "@cf/google/gemma-7b-it-lora" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/meta/llama-3-8b-instruct" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/meta/llama-3-8b-instruct-awq" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@cf/meta/llama-3.2-3b-instruct" | "@cf/meta/llama-3.2-1b-instruct" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@cf/meta/llama-3.2-11b-vision-instruct", "@cf/baai/bge-base-en-v1.5" | "@cf/baai/bge-small-en-v1.5" | "@cf/baai/bge-large-en-v1.5">>;
12
+ custom(args: AiRequestConfig): Promise<OpenAICompatibleProvider<string, string, string>>;
13
+ googleAi(args: AiRequestConfig): Promise<GoogleGenerativeAIProvider>;
10
14
  }
@@ -1,5 +1,5 @@
1
1
  import { Helpers } from '@chainfuse/helpers';
2
- import { enabledCloudflareLlmEmbeddingProviders, enabledCloudflareLlmProviders } from '@chainfuse/types';
2
+ import { AiModels, enabledCloudflareLlmProviders } from '@chainfuse/types';
3
3
  import { APICallError, experimental_customProvider as customProvider, TypeValidationError, experimental_wrapLanguageModel as wrapLanguageModel } from 'ai';
4
4
  import { ZodError } from 'zod';
5
5
  import { AiBase } from '../base.mjs';
@@ -17,7 +17,7 @@ export class AiCustomProviders extends AiBase {
17
17
  const acc = await accPromise;
18
18
  // @ts-expect-error override for types
19
19
  acc[model] = wrapLanguageModel({
20
- model: (await raw.azOpenai(args, server.id))(model),
20
+ model: (await raw.azOpenai(args, server))(model),
21
21
  middleware: {
22
22
  wrapGenerate: async ({ doGenerate, model, params }) => {
23
23
  try {
@@ -40,7 +40,7 @@ export class AiCustomProviders extends AiBase {
40
40
  if (args.logging ?? this.config.environment !== 'production')
41
41
  console.error('ai', 'custom provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), this.chalk.blue('FALLBACK'), nextServer.id, 'REMAINING', JSON.stringify(leftOverServers.slice(leftOverServers.indexOf(nextServer) + 1).map((s) => s.id)));
42
42
  // Must be double awaited to prevent a promise from being returned
43
- return await (await raw.azOpenai({ ...args, idempotencyId }, nextServer.id))(model.modelId).doGenerate(params);
43
+ return await (await raw.azOpenai({ ...args, idempotencyId }, nextServer))(model.modelId).doGenerate(params);
44
44
  }
45
45
  catch (nextServerError) {
46
46
  if (APICallError.isInstance(nextServerError)) {
@@ -70,7 +70,7 @@ export class AiCustomProviders extends AiBase {
70
70
  textEmbeddingModels: await server.textEmbeddingModelAvailability.reduce(async (accPromise, model) => {
71
71
  const acc = await accPromise;
72
72
  // @ts-expect-error override for types
73
- acc[model] = (await raw.azOpenai(args, server.id)).textEmbeddingModel(model);
73
+ acc[model] = (await raw.azOpenai(args, server)).textEmbeddingModel(model);
74
74
  return acc;
75
75
  }, Promise.resolve({})),
76
76
  // An optional fallback provider to use when a requested model is not found in the custom provider.
@@ -139,13 +139,7 @@ export class AiCustomProviders extends AiBase {
139
139
  });
140
140
  return acc;
141
141
  }, Promise.resolve({})),
142
- // @ts-expect-error override for types
143
- textEmbeddingModels: await enabledCloudflareLlmEmbeddingProviders.reduce(async (accPromise, model) => {
144
- const acc = await accPromise;
145
- // @ts-expect-error override for types
146
- acc[model] = (await raw.restWorkersAi(args)).textEmbeddingModel(model);
147
- return acc;
148
- }, Promise.resolve({})),
142
+ fallbackProvider: await raw.restWorkersAi(args),
149
143
  });
150
144
  }
151
145
  else {
@@ -164,4 +158,20 @@ export class AiCustomProviders extends AiBase {
164
158
  }) as CloudflareOpenAIProvider;*/
165
159
  }
166
160
  }
161
+ custom(args) {
162
+ return new AiRawProviders(this.config).custom(args);
163
+ }
164
+ async googleAi(args) {
165
+ const fallbackProvider = await new AiRawProviders(this.config).googleAi(args);
166
+ return customProvider({
167
+ languageModels: {
168
+ // provider:actual model name:extra
169
+ [AiModels.LanguageModels.GoogleGenerativeAi.gemini_flash_beta_search.split(':').slice(1).join(':')]: fallbackProvider(AiModels.LanguageModels.GoogleGenerativeAi.gemini_flash_beta_search.split(':')[1], { useSearchGrounding: true }),
170
+ [AiModels.LanguageModels.GoogleGenerativeAi.gemini_flash_search.split(':').slice(1).join(':')]: fallbackProvider(AiModels.LanguageModels.GoogleGenerativeAi.gemini_flash_search.split(':')[1], { useSearchGrounding: true }),
171
+ [AiModels.LanguageModels.GoogleGenerativeAi.gemini_pro_search.split(':').slice(1).join(':')]: fallbackProvider(AiModels.LanguageModels.GoogleGenerativeAi.gemini_pro_search.split(':')[1], { useSearchGrounding: true }),
172
+ },
173
+ fallbackProvider,
174
+ // GoogleGenerativeAi
175
+ });
176
+ }
167
177
  }
@@ -1,9 +1,15 @@
1
+ import type { OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
2
+ import type { cloudflareModelPossibilities } from '@chainfuse/types';
1
3
  import { AiBase } from '../base.mjs';
4
+ import type { Server } from '../serverSelector/types.mjs';
2
5
  import type { AiRequestConfig } from '../types.mjs';
3
6
  export declare class AiRawProviders extends AiBase {
4
7
  private readonly cacheTtl;
8
+ private updateGatewayLog;
5
9
  oaiOpenai(args: AiRequestConfig): Promise<import("@ai-sdk/openai").OpenAIProvider>;
6
- azOpenai(args: AiRequestConfig, server: string): Promise<import("@ai-sdk/azure").AzureOpenAIProvider>;
10
+ azOpenai(args: AiRequestConfig, server: Server): Promise<import("@ai-sdk/azure").AzureOpenAIProvider>;
7
11
  anthropic(args: AiRequestConfig): Promise<import("@ai-sdk/anthropic").AnthropicProvider>;
8
- restWorkersAi(args: AiRequestConfig): Promise<import("@ai-sdk/openai").OpenAIProvider>;
12
+ custom(args: AiRequestConfig): Promise<OpenAICompatibleProvider<string, string, string>>;
13
+ googleAi(args: AiRequestConfig): Promise<import("@ai-sdk/google").GoogleGenerativeAIProvider>;
14
+ restWorkersAi(args: AiRequestConfig): Promise<OpenAICompatibleProvider<cloudflareModelPossibilities<'Text Generation'>, cloudflareModelPossibilities<'Text Generation'>, cloudflareModelPossibilities<'Text Embeddings'>>>;
9
15
  }
@@ -1,8 +1,38 @@
1
- import { BufferHelpers, CryptoHelpers, Helpers } from '@chainfuse/helpers';
1
+ import { BufferHelpers, CryptoHelpers, DnsHelpers, Helpers } from '@chainfuse/helpers';
2
+ import haversine from 'haversine-distance';
3
+ import { z } from 'zod';
2
4
  import { AiBase } from '../base.mjs';
3
5
  export class AiRawProviders extends AiBase {
4
6
  // 2628288 seconds is what cf defines as 1 month in their cache rules
5
7
  cacheTtl = 2628288;
8
+ async updateGatewayLog(response, metadataHeader, startRoundTrip, modelTime) {
9
+ /**
10
+ * @todo `cloudflare` rest package not updated to this endpoint yet
11
+ */
12
+ const updateMetadata = fetch(new URL(['client', 'v4', 'accounts', this.config.gateway.accountId, 'ai-gateway', 'gateways', this.config.environment, 'logs', response.headers.get('cf-aig-log-id')].join('/'), 'https://api.cloudflare.com'), {
13
+ method: 'PATCH',
14
+ headers: {
15
+ Authorization: `Bearer ${this.config.gateway.apiToken}`,
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ body: JSON.stringify({
19
+ metadata: {
20
+ ...metadataHeader,
21
+ timing: JSON.stringify({
22
+ fromCache: response.headers.get('cf-aig-cache-status')?.toLowerCase() === 'hit',
23
+ totalRoundtripTime: performance.now() - startRoundTrip,
24
+ modelTime,
25
+ }),
26
+ },
27
+ }),
28
+ });
29
+ if (this.config.backgroundContext) {
30
+ this.config.backgroundContext.waitUntil(updateMetadata);
31
+ }
32
+ else {
33
+ await updateMetadata;
34
+ }
35
+ }
6
36
  oaiOpenai(args) {
7
37
  return import('@ai-sdk/openai').then(async ({ createOpenAI }) => createOpenAI({
8
38
  baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'openai'].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
@@ -29,6 +59,7 @@ export class AiRawProviders extends AiBase {
29
59
  },
30
60
  compatibility: 'strict',
31
61
  fetch: async (input, rawInit) => {
62
+ const startRoundTrip = performance.now();
32
63
  const headers = new Headers(rawInit?.headers);
33
64
  const metadataHeader = JSON.parse(headers.get('cf-aig-metadata'));
34
65
  if (metadataHeader.idempotencyId.split('-').length === 4) {
@@ -40,6 +71,7 @@ export class AiRawProviders extends AiBase {
40
71
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
41
72
  if (args.logging ?? this.config.environment !== 'production')
42
73
  console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
74
+ await this.updateGatewayLog(response, metadataHeader, startRoundTrip, response.headers.has('openai-processing-ms') ? parseInt(response.headers.get('openai-processing-ms')) : undefined);
43
75
  // Inject it to have it available for retries
44
76
  const mutableHeaders = new Headers(response.headers);
45
77
  mutableHeaders.set('X-Idempotency-Id', metadataHeader.idempotencyId);
@@ -57,13 +89,13 @@ export class AiRawProviders extends AiBase {
57
89
  }
58
90
  azOpenai(args, server) {
59
91
  return import('@ai-sdk/azure').then(async ({ createAzure }) => createAzure({
60
- apiKey: this.config.providers.azureOpenAi.apiTokens[`AZURE_API_KEY_${server.toUpperCase().replaceAll('-', '_')}`],
92
+ apiKey: this.config.providers.azureOpenAi.apiTokens[`AZURE_API_KEY_${server.id.toUpperCase().replaceAll('-', '_')}`],
61
93
  /**
62
94
  * @link https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#api-specs
63
95
  * From the table, pick the `Latest GA release` for `Data plane - inference`
64
96
  */
65
97
  apiVersion: '2024-10-21',
66
- baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'azure-openai', server.toLowerCase()].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
98
+ baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'azure-openai', server.id.toLowerCase()].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
67
99
  headers: {
68
100
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
69
101
  'cf-aig-metadata': JSON.stringify({
@@ -72,7 +104,11 @@ export class AiRawProviders extends AiBase {
72
104
  // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
73
105
  idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
74
106
  serverInfo: JSON.stringify({
75
- name: 'openai',
107
+ name: `azure-${server.id}`,
108
+ distance: haversine({
109
+ lat: Helpers.precisionFloat(this.config.geoRouting?.userCoordinate?.lat ?? '0'),
110
+ lon: Helpers.precisionFloat(this.config.geoRouting?.userCoordinate?.lon ?? '0'),
111
+ }, server.coordinate),
76
112
  }),
77
113
  /**
78
114
  * Blank at first, add after request finishes
@@ -84,6 +120,7 @@ export class AiRawProviders extends AiBase {
84
120
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
85
121
  },
86
122
  fetch: async (input, rawInit) => {
123
+ const startRoundTrip = performance.now();
87
124
  const headers = new Headers(rawInit?.headers);
88
125
  const metadataHeader = JSON.parse(headers.get('cf-aig-metadata'));
89
126
  if (metadataHeader.idempotencyId.split('-').length === 4) {
@@ -95,6 +132,7 @@ export class AiRawProviders extends AiBase {
95
132
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
96
133
  if (args.logging ?? this.config.environment !== 'production')
97
134
  console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
135
+ await this.updateGatewayLog(response, metadataHeader, startRoundTrip, response.headers.has('x-envoy-upstream-service-time') ? parseInt(response.headers.get('x-envoy-upstream-service-time')) : undefined);
98
136
  // Inject it to have it available for retries
99
137
  const mutableHeaders = new Headers(response.headers);
100
138
  mutableHeaders.set('X-Idempotency-Id', metadataHeader.idempotencyId);
@@ -122,7 +160,168 @@ export class AiRawProviders extends AiBase {
122
160
  // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
123
161
  idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
124
162
  serverInfo: JSON.stringify({
125
- name: 'openai',
163
+ name: 'anthropic',
164
+ }),
165
+ /**
166
+ * Blank at first, add after request finishes
167
+ * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
168
+ */
169
+ timing: JSON.stringify({}),
170
+ }),
171
+ ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
172
+ ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
173
+ },
174
+ fetch: async (input, rawInit) => {
175
+ const startRoundTrip = performance.now();
176
+ const headers = new Headers(rawInit?.headers);
177
+ const metadataHeader = JSON.parse(headers.get('cf-aig-metadata'));
178
+ if (metadataHeader.idempotencyId.split('-').length === 4) {
179
+ metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
180
+ headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
181
+ }
182
+ if (args.logging ?? this.config.environment !== 'production')
183
+ console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), this.chalk.magenta(rawInit?.method), this.chalk.magenta(new URL(new Request(input).url).pathname));
184
+ return fetch(input, { ...rawInit, headers }).then(async (response) => {
185
+ if (args.logging ?? this.config.environment !== 'production')
186
+ console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
187
+ await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
188
+ // Inject it to have it available for retries
189
+ const mutableHeaders = new Headers(response.headers);
190
+ mutableHeaders.set('X-Idempotency-Id', metadataHeader.idempotencyId);
191
+ if (response.ok) {
192
+ return new Response(response.body, { ...response, headers: mutableHeaders });
193
+ }
194
+ else {
195
+ const [body1, body2] = response.body.tee();
196
+ console.error('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), this.chalk.red(JSON.stringify(await new Response(body1, response).json())));
197
+ return new Response(body2, { ...response, headers: mutableHeaders });
198
+ }
199
+ });
200
+ },
201
+ }));
202
+ }
203
+ custom(args) {
204
+ if (this.config.providers.custom) {
205
+ // Verify that the custom provider url is a valid URL
206
+ return z
207
+ .string()
208
+ .trim()
209
+ .url()
210
+ .transform((url) => new URL(url))
211
+ .parseAsync(this.config.providers.custom.url)
212
+ .then((customProviderUrl) =>
213
+ // Verify that the custom provider url is not an IP address
214
+ z
215
+ .string()
216
+ .trim()
217
+ .ip()
218
+ .safeParseAsync(customProviderUrl.hostname)
219
+ .then(async ({ success }) => {
220
+ if (success) {
221
+ throw new Error('IP custom providers not allowed');
222
+ }
223
+ else {
224
+ // Run domain through ZT policies
225
+ const doh = new DnsHelpers(new URL('dns-query', `https://${this.config.providers.custom?.dohId}.cloudflare-gateway.com`));
226
+ const aCheck = doh.query(customProviderUrl.hostname, 'A', undefined, undefined, 2 * 1000);
227
+ const aaaaCheck = doh.query(customProviderUrl.hostname, 'AAAA', undefined, undefined, 2 * 1000);
228
+ return Promise.allSettled([aCheck, aaaaCheck]).then((checks) => {
229
+ const fulfulledChecks = checks.filter((check) => check.status === 'fulfilled');
230
+ /**
231
+ * Blocked domains return 0.0.0.0 or :: as the answer
232
+ * @link https://developers.cloudflare.com/cloudflare-one/policies/gateway/block-page/
233
+ */
234
+ if (fulfulledChecks.length > 0 && fulfulledChecks.some((obj) => 'Answer' in obj.value && Array.isArray(obj.value.Answer) && obj.value.Answer.some((answer) => answer.data !== '0.0.0.0' && answer.data !== '::'))) {
235
+ // ZT Pass, perform the calls
236
+ return import('@ai-sdk/openai-compatible').then(async ({ createOpenAICompatible }) => createOpenAICompatible({
237
+ baseURL: customProviderUrl.toString(),
238
+ ...(this.config.providers.custom?.apiToken && { apiKey: this.config.providers.custom.apiToken }),
239
+ headers: {
240
+ // ZT Auth if present
241
+ ...(this.config.providers.custom &&
242
+ 'clientId' in this.config.providers.custom &&
243
+ this.config.providers.custom.clientId &&
244
+ 'clientSecret' in this.config.providers.custom &&
245
+ this.config.providers.custom.clientSecret && {
246
+ 'CF-Access-Client-Id': this.config.providers.custom.clientId,
247
+ 'CF-Access-Client-Secret': this.config.providers.custom.clientSecret,
248
+ }),
249
+ 'X-Dataspace-Id': (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
250
+ 'X-Executor': JSON.stringify(args.executor),
251
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
252
+ 'X-Idempotency-Id': args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
253
+ // Request to skip or custom cache duration (no guarantee that upstream server will respect it)
254
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
255
+ ...((args.skipCache || args.cache) && { 'Cache-Control': [args.skipCache && 'no-cache', args.cache && `max-age=${typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache}`].join(', ') }),
256
+ },
257
+ name: 'custom',
258
+ fetch: async (input, rawInit) => {
259
+ const headers = new Headers(rawInit?.headers);
260
+ let idempotencyId = headers.get('X-Idempotency-Id');
261
+ if (idempotencyId.split('-').length === 4) {
262
+ idempotencyId = `${idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
263
+ headers.set('X-Idempotency-Id', idempotencyId);
264
+ }
265
+ if (args.logging ?? this.config.environment !== 'production')
266
+ console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), this.chalk.magenta(rawInit?.method), this.chalk.magenta(new URL(new Request(input).url).pathname));
267
+ return fetch(input, { ...rawInit, headers }).then(async (response) => {
268
+ if (args.logging ?? this.config.environment !== 'production')
269
+ console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
270
+ // Inject it to have it available for retries
271
+ const mutableHeaders = new Headers(response.headers);
272
+ mutableHeaders.set('X-Idempotency-Id', idempotencyId);
273
+ if (response.ok) {
274
+ return new Response(response.body, { ...response, headers: mutableHeaders });
275
+ }
276
+ else {
277
+ const [body1, body2] = response.body.tee();
278
+ console.error('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), this.chalk.red(JSON.stringify(await new Response(body1, response).json())));
279
+ return new Response(body2, { ...response, headers: mutableHeaders });
280
+ }
281
+ });
282
+ },
283
+ }));
284
+ }
285
+ else {
286
+ throw new Error('Failed ZT check on custom provider url');
287
+ }
288
+ });
289
+ }
290
+ }))
291
+ .catch(() => {
292
+ throw new Error('Invalid custom provider url');
293
+ });
294
+ }
295
+ else {
296
+ // This always gets called, only throw error if actually being used
297
+ return import('@ai-sdk/openai-compatible').then(async ({ createOpenAICompatible }) => createOpenAICompatible({
298
+ // Dummy url that'll never be hit
299
+ baseURL: 'https://sushidata.com',
300
+ name: 'custom',
301
+ // eslint-disable-next-line @typescript-eslint/require-await
302
+ fetch: async () => {
303
+ throw new Error('Custom provider not configured');
304
+ },
305
+ }));
306
+ }
307
+ }
308
+ googleAi(args) {
309
+ return import('@ai-sdk/google').then(async ({ createGoogleGenerativeAI }) => createGoogleGenerativeAI({
310
+ /**
311
+ * `v1beta` is the only one that supports function calls as of now
312
+ * @link https://ai.google.dev/gemini-api/docs/api-versions
313
+ */
314
+ baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'google-ai-studio', 'v1beta'].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
315
+ apiKey: this.config.providers.googleAi.apiToken,
316
+ headers: {
317
+ 'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
318
+ 'cf-aig-metadata': JSON.stringify({
319
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
320
+ executor: JSON.stringify(args.executor),
321
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
322
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
323
+ serverInfo: JSON.stringify({
324
+ name: 'googleai',
126
325
  }),
127
326
  /**
128
327
  * Blank at first, add after request finishes
@@ -134,6 +333,7 @@ export class AiRawProviders extends AiBase {
134
333
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
135
334
  },
136
335
  fetch: async (input, rawInit) => {
336
+ const startRoundTrip = performance.now();
137
337
  const headers = new Headers(rawInit?.headers);
138
338
  const metadataHeader = JSON.parse(headers.get('cf-aig-metadata'));
139
339
  if (metadataHeader.idempotencyId.split('-').length === 4) {
@@ -145,6 +345,7 @@ export class AiRawProviders extends AiBase {
145
345
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
146
346
  if (args.logging ?? this.config.environment !== 'production')
147
347
  console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
348
+ await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
148
349
  // Inject it to have it available for retries
149
350
  const mutableHeaders = new Headers(response.headers);
150
351
  mutableHeaders.set('X-Idempotency-Id', metadataHeader.idempotencyId);
@@ -161,7 +362,7 @@ export class AiRawProviders extends AiBase {
161
362
  }));
162
363
  }
163
364
  restWorkersAi(args) {
164
- return import('@ai-sdk/openai').then(async ({ createOpenAI }) => createOpenAI({
365
+ return import('@ai-sdk/openai-compatible').then(async ({ createOpenAICompatible }) => createOpenAICompatible({
165
366
  baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'workers-ai', 'v1'].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
166
367
  apiKey: this.config.providers.workersAi.apiToken,
167
368
  headers: {
@@ -172,7 +373,7 @@ export class AiRawProviders extends AiBase {
172
373
  // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
173
374
  idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
174
375
  serverInfo: JSON.stringify({
175
- name: 'openai',
376
+ name: 'cloudflare',
176
377
  }),
177
378
  /**
178
379
  * Blank at first, add after request finishes
@@ -183,9 +384,9 @@ export class AiRawProviders extends AiBase {
183
384
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
184
385
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
185
386
  },
186
- compatibility: 'compatible',
187
387
  name: 'workersai',
188
388
  fetch: async (input, rawInit) => {
389
+ const startRoundTrip = performance.now();
189
390
  const headers = new Headers(rawInit?.headers);
190
391
  const metadataHeader = JSON.parse(headers.get('cf-aig-metadata'));
191
392
  if (metadataHeader.idempotencyId.split('-').length === 4) {
@@ -197,6 +398,7 @@ export class AiRawProviders extends AiBase {
197
398
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
198
399
  if (args.logging ?? this.config.environment !== 'production')
199
400
  console.info('ai', 'raw provider', this.chalk.rgb(...Helpers.uniqueIdColor(metadataHeader.idempotencyId))(`[${metadataHeader.idempotencyId}]`), response.ok ? this.chalk.green(response.status) : this.chalk.red(response.status), response.ok ? this.chalk.green(new URL(response.url).pathname) : this.chalk.red(new URL(response.url).pathname));
401
+ await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
200
402
  // Inject it to have it available for retries
201
403
  const mutableHeaders = new Headers(response.headers);
202
404
  mutableHeaders.set('X-Idempotency-Id', metadataHeader.idempotencyId);
@@ -1,6 +1,6 @@
1
1
  import type { OpenAIChatSettings, OpenAIEmbeddingSettings } from '@ai-sdk/openai/internal';
2
2
  import type { EmbeddingModelV1, LanguageModelV1 } from '@ai-sdk/provider';
3
- import type { AzureChatModels, AzureEmbeddingModels, cloudflareModelPossibilities } from '@chainfuse/types';
3
+ import type { AzureChatModels, AzureEmbeddingModels } from '@chainfuse/types';
4
4
  import type { Provider } from 'ai';
5
5
  export interface AzureOpenAIProvider extends Provider {
6
6
  (deploymentId: AzureChatModels, settings?: OpenAIChatSettings): LanguageModelV1;
@@ -13,14 +13,3 @@ export interface AzureOpenAIProvider extends Provider {
13
13
  */
14
14
  textEmbeddingModel(deploymentId: AzureEmbeddingModels, settings?: OpenAIEmbeddingSettings): EmbeddingModelV1<string>;
15
15
  }
16
- export interface CloudflareOpenAIProvider extends Provider {
17
- (deploymentId: cloudflareModelPossibilities<'Text Generation'>, settings?: OpenAIChatSettings): LanguageModelV1;
18
- /**
19
- Creates an Azure OpenAI chat model for text generation.
20
- */
21
- languageModel(deploymentId: cloudflareModelPossibilities<'Text Generation'>, settings?: OpenAIChatSettings): LanguageModelV1;
22
- /**
23
- Creates an Azure OpenAI model for text embeddings.
24
- */
25
- textEmbeddingModel(deploymentId: cloudflareModelPossibilities<'Text Embeddings'>, settings?: OpenAIEmbeddingSettings): EmbeddingModelV1<string>;
26
- }
@@ -5,7 +5,9 @@ export declare class AiRegistry extends AiBase {
5
5
  openai: import("@ai-sdk/openai").OpenAIProvider;
6
6
  azure: import("./providers/types.mjs").AzureOpenAIProvider;
7
7
  anthropic: import("@ai-sdk/anthropic").AnthropicProvider;
8
- workersai: import("./providers/types.mjs").CloudflareOpenAIProvider;
8
+ custom: import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<string, string, string>;
9
+ 'google.generative-ai': import("@ai-sdk/google").GoogleGenerativeAIProvider;
10
+ workersai: import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<"@cf/meta/llama-2-7b-chat-int8" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/meta/llama-2-7b-chat-fp16" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/openchat/openchat-3.5-0106" | "@cf/tiiuae/falcon-7b-instruct" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/qwen/qwen1.5-0.5b-chat" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@cf/microsoft/phi-2" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@hf/nexusflow/starling-lm-7b-beta" | "@hf/google/gemma-7b-it" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/google/gemma-2b-it-lora" | "@cf/google/gemma-7b-it-lora" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/meta/llama-3-8b-instruct" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/meta/llama-3-8b-instruct-awq" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@cf/meta/llama-3.2-3b-instruct" | "@cf/meta/llama-3.2-1b-instruct" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@cf/meta/llama-3.2-11b-vision-instruct", "@cf/meta/llama-2-7b-chat-int8" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/meta/llama-2-7b-chat-fp16" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/openchat/openchat-3.5-0106" | "@cf/tiiuae/falcon-7b-instruct" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/qwen/qwen1.5-0.5b-chat" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@cf/microsoft/phi-2" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@hf/nexusflow/starling-lm-7b-beta" | "@hf/google/gemma-7b-it" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/google/gemma-2b-it-lora" | "@cf/google/gemma-7b-it-lora" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/meta/llama-3-8b-instruct" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/meta/llama-3-8b-instruct-awq" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@cf/meta/llama-3.2-3b-instruct" | "@cf/meta/llama-3.2-1b-instruct" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@cf/meta/llama-3.2-11b-vision-instruct", "@cf/baai/bge-base-en-v1.5" | "@cf/baai/bge-small-en-v1.5" | "@cf/baai/bge-large-en-v1.5">;
9
11
  }>>;
10
12
  registry(args: AiRequestConfig): Promise<import("ai").Provider>;
11
13
  }
package/dist/registry.mjs CHANGED
@@ -7,6 +7,8 @@ export class AiRegistry extends AiBase {
7
7
  openai: await new AiCustomProviders(this.config).oaiOpenai(args),
8
8
  azure: await new AiCustomProviders(this.config).azOpenai(args),
9
9
  anthropic: await new AiCustomProviders(this.config).anthropic(args),
10
+ custom: await new AiCustomProviders(this.config).custom(args),
11
+ 'google.generative-ai': await new AiCustomProviders(this.config).googleAi(args),
10
12
  workersai: await new AiCustomProviders(this.config).cfWorkersAi(args),
11
13
  });
12
14
  }
package/dist/types.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { PrefixedUuid, RawCoordinate, UuidExport } from '@chainfuse/types';
2
- import type { Ai, IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
2
+ import type { Ai, ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
3
3
  import type haversine from 'haversine-distance';
4
4
  export interface AiConfig {
5
5
  gateway: {
@@ -13,10 +13,13 @@ export interface AiConfig {
13
13
  };
14
14
  environment: 'production' | 'preview';
15
15
  providers: AiConfigProviders;
16
+ backgroundContext?: ExecutionContext;
16
17
  }
17
18
  export interface AiConfigProviders {
18
19
  anthropic: AiConfigAnthropic;
19
20
  azureOpenAi: AiConfigAzOpenai;
21
+ custom?: AiConfigCustom;
22
+ googleAi: AiConfigGoogleAi;
20
23
  openAi: AiConfigOaiOpenai;
21
24
  workersAi: AiConfigWorkersai;
22
25
  }
@@ -26,10 +29,27 @@ export interface AiConfigAnthropic {
26
29
  export interface AiConfigAzOpenai {
27
30
  apiTokens: Record<`AZURE_API_KEY_${string}`, string>;
28
31
  }
32
+ export interface AiConfigCustomBase {
33
+ /**
34
+ * For safety resons, no direct IPs allowed.
35
+ */
36
+ url: string;
37
+ dohId: string;
38
+ apiToken?: string;
39
+ }
40
+ export type AiConfigCustom = AiConfigCustomBase | (AiConfigCustomBase & AiConfigCustomZT);
41
+ export interface AiConfigCustomZT {
42
+ clientId: string;
43
+ clientSecret: string;
44
+ }
45
+ export interface AiConfigGoogleAi {
46
+ apiToken: string;
47
+ }
29
48
  export interface AiConfigOaiOpenai {
30
49
  apiToken: `sk-${string}`;
31
50
  organization: `org-${string}`;
32
51
  }
52
+ export type AiConfigWorkersai = AiConfigWorkersaiRest | AiConfigWorkersaiBinding;
33
53
  export interface AiConfigWorkersaiRest {
34
54
  apiToken: string;
35
55
  }
@@ -37,7 +57,6 @@ export interface AiConfigWorkersaiRest {
37
57
  * @deprecated Not functional. Use REST instead
38
58
  */
39
59
  export type AiConfigWorkersaiBinding = Ai;
40
- export type AiConfigWorkersai = AiConfigWorkersaiRest | AiConfigWorkersaiBinding;
41
60
  /**
42
61
  * It's a UUID, but the last block is SHA256 of the request body
43
62
  */
@@ -74,7 +93,7 @@ export interface AiRequestConfig {
74
93
  skipCache?: boolean;
75
94
  }
76
95
  export interface AiRequestMetadataServerInfo {
77
- name: 'anthropic' | 'cloudflare' | 'openai';
96
+ name: 'anthropic' | 'cloudflare' | 'googleai' | 'openai';
78
97
  }
79
98
  export interface AiRequestMetadataServerInfoWithLocation {
80
99
  name: `${'azure' | 'google'}-${string}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainfuse/ai-tools",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "",
5
5
  "author": "ChainFuse",
6
6
  "homepage": "https://github.com/ChainFuse/packages/tree/main/packages/ai-tools#readme",
@@ -48,19 +48,21 @@
48
48
  },
49
49
  "prettier": "@demosjarco/prettier-config",
50
50
  "dependencies": {
51
- "@ai-sdk/anthropic": "^1.0.6",
52
- "@ai-sdk/azure": "^1.0.15",
51
+ "@ai-sdk/anthropic": "^1.0.8",
52
+ "@ai-sdk/azure": "^1.0.18",
53
+ "@ai-sdk/google": "^1.0.14",
53
54
  "@ai-sdk/openai": "^1.0.5",
54
- "@chainfuse/helpers": "^0.5.0",
55
- "@chainfuse/types": "^1.4.0",
56
- "ai": "^4.0.25",
55
+ "@ai-sdk/openai-compatible": "^0.0.15",
56
+ "@chainfuse/helpers": "^0.6.0",
57
+ "@chainfuse/types": "^1.4.1",
58
+ "ai": "^4.0.31",
57
59
  "chalk": "^5.4.1",
58
60
  "haversine-distance": "^1.2.3",
59
61
  "workers-ai-provider": "^0.0.10"
60
62
  },
61
63
  "devDependencies": {
62
- "@cloudflare/workers-types": "^4.20241230.0",
63
- "openai": "^4.77.3"
64
+ "@cloudflare/workers-types": "^4.20250109.0",
65
+ "openai": "^4.77.4"
64
66
  },
65
- "gitHead": "6af42c16348b8a841a6c12dd15fc273a04d862b6"
67
+ "gitHead": "75406cd04aedccc51d9972a79dfbbd5ce7fe6945"
66
68
  }