@chainfuse/ai-tools 0.7.1 → 0.9.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/dist/models.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { LanguageModelValues, TextEmbeddingModelValues } from '@chainfuse/types';
2
- import { wrapLanguageModel, type embed, type embedMany } from 'ai';
2
+ import type { embed, embedMany, wrapLanguageModel } from 'ai';
3
3
  import { AiBase } from './base.mjs';
4
- import { AiRegistry } from './registry.mjs';
4
+ import type { AiRegistry } from './registry.mjs';
5
5
  import type { AiRequestConfig } from './types.mjs';
6
6
  type ProvidersReturnType = Awaited<ReturnType<AiRegistry['providers']>>;
7
7
  type ValidProviders = keyof ProvidersReturnType;
package/dist/models.mjs CHANGED
@@ -1,15 +1,15 @@
1
- import { extractReasoningMiddleware, wrapLanguageModel } from 'ai';
2
1
  import { AiBase } from './base.mjs';
3
- import { AiRegistry } from './registry.mjs';
4
2
  export class AiModel extends AiBase {
5
3
  wrappedLanguageModel(args, modelOrProvider, model) {
6
- return new AiRegistry(this.config).registry(args).then((registry) => wrapLanguageModel({
4
+ return import('./registry.mjs')
5
+ .then(({ AiRegistry }) => new AiRegistry(this.config).registry(args))
6
+ .then((registry) => import('ai').then(({ extractReasoningMiddleware, wrapLanguageModel }) => wrapLanguageModel({
7
7
  model: registry.languageModel(model ? `${modelOrProvider}:${model}` : modelOrProvider),
8
8
  middleware: [extractReasoningMiddleware({ tagName: 'think' }), this.middleware],
9
- }));
9
+ })));
10
10
  }
11
11
  wrappedTextEmbeddingModel(args, modelOrProvider, model) {
12
- return new AiRegistry(this.config).registry(args).then((registry) => registry.textEmbeddingModel(model ? `${modelOrProvider}:${model}` : modelOrProvider));
12
+ return import('./registry.mjs').then(({ AiRegistry }) => new AiRegistry(this.config).registry(args)).then((registry) => registry.textEmbeddingModel(model ? `${modelOrProvider}:${model}` : modelOrProvider));
13
13
  }
14
14
  get middleware() {
15
15
  return {};
@@ -1,11 +1,11 @@
1
1
  import type { GoogleGenerativeAIProvider } from '@ai-sdk/google';
2
2
  import type { OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
3
3
  import { AiBase } from '../base.mjs';
4
- import type { AiRequestConfig } from '../types.mjs';
4
+ import type { AiRequestConfig, AzureServers } from '../types.mjs';
5
5
  import type { AzureOpenAIProvider, WorkersAIProvider } from './types.mjs';
6
6
  export declare class AiCustomProviders extends AiBase {
7
7
  oaiOpenai(args: AiRequestConfig): Promise<import("@ai-sdk/openai").OpenAIProvider>;
8
- azOpenai(args: AiRequestConfig, [server, ...servers]?: import("../serverSelector/types.mts").Server[]): Promise<AzureOpenAIProvider>;
8
+ azOpenai(args: AiRequestConfig, filteredServers?: AzureServers): Promise<AzureOpenAIProvider>;
9
9
  anthropic(args: AiRequestConfig): Promise<import("@ai-sdk/anthropic").AnthropicProvider>;
10
10
  private static workersAiIsRest;
11
11
  cfWorkersAi(args: AiRequestConfig): Promise<WorkersAIProvider | OpenAICompatibleProvider<"@cf/qwen/qwen1.5-0.5b-chat" | "@cf/google/gemma-2b-it-lora" | "@hf/nexusflow/starling-lm-7b-beta" | "@cf/meta/llama-3-8b-instruct" | "@cf/meta/llama-3.2-3b-instruct" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@cf/meta/llama-guard-3-8b" | "@cf/meta/llama-2-7b-chat-fp16" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/meta/llama-2-7b-chat-int8" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/meta/llama-3.2-1b-instruct" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/tiiuae/falcon-7b-instruct" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@cf/google/gemma-7b-it-lora" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/meta/llama-3-8b-instruct-awq" | "@cf/meta/llama-3.2-11b-vision-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/microsoft/phi-2" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@hf/google/gemma-7b-it" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/openchat/openchat-3.5-0106", "@cf/qwen/qwen1.5-0.5b-chat" | "@cf/google/gemma-2b-it-lora" | "@hf/nexusflow/starling-lm-7b-beta" | "@cf/meta/llama-3-8b-instruct" | "@cf/meta/llama-3.2-3b-instruct" | "@hf/thebloke/llamaguard-7b-awq" | "@hf/thebloke/neural-chat-7b-v3-1-awq" | "@cf/meta/llama-guard-3-8b" | "@cf/meta/llama-2-7b-chat-fp16" | "@cf/mistral/mistral-7b-instruct-v0.1" | "@cf/mistral/mistral-7b-instruct-v0.2-lora" | "@cf/tinyllama/tinyllama-1.1b-chat-v1.0" | "@hf/mistral/mistral-7b-instruct-v0.2" | "@cf/fblgit/una-cybertron-7b-v2-bf16" | "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b" | "@cf/thebloke/discolm-german-7b-v1-awq" | "@cf/meta/llama-2-7b-chat-int8" | "@cf/meta/llama-3.1-8b-instruct-fp8" | "@hf/thebloke/mistral-7b-instruct-v0.1-awq" | "@cf/qwen/qwen1.5-7b-chat-awq" | "@cf/meta/llama-3.2-1b-instruct" | "@hf/thebloke/llama-2-13b-chat-awq" | "@hf/thebloke/deepseek-coder-6.7b-base-awq" | "@cf/meta-llama/llama-2-7b-chat-hf-lora" | "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | "@hf/thebloke/openhermes-2.5-mistral-7b-awq" | "@hf/thebloke/deepseek-coder-6.7b-instruct-awq" | "@cf/deepseek-ai/deepseek-math-7b-instruct" | "@cf/tiiuae/falcon-7b-instruct" | "@hf/nousresearch/hermes-2-pro-mistral-7b" | "@cf/meta/llama-3.1-8b-instruct" | "@cf/meta/llama-3.1-8b-instruct-awq" | "@hf/thebloke/zephyr-7b-beta-awq" | "@cf/google/gemma-7b-it-lora" | "@cf/qwen/qwen1.5-1.8b-chat" | "@cf/meta/llama-3-8b-instruct-awq" | "@cf/meta/llama-3.2-11b-vision-instruct" | "@cf/defog/sqlcoder-7b-2" | "@cf/microsoft/phi-2" | "@hf/meta-llama/meta-llama-3-8b-instruct" | "@hf/google/gemma-7b-it" | "@cf/qwen/qwen1.5-14b-chat-awq" | "@cf/openchat/openchat-3.5-0106", "@cf/baai/bge-m3" | "@cf/baai/bge-small-en-v1.5" | "@cf/baai/bge-base-en-v1.5" | "@cf/baai/bge-large-en-v1.5">>;
@@ -3,21 +3,24 @@ import { AiModels, enabledCloudflareLlmProviders } from '@chainfuse/types';
3
3
  import { APICallError, customProvider, TypeValidationError, wrapLanguageModel } from 'ai';
4
4
  import { ZodError } from 'zod';
5
5
  import { AiBase } from '../base.mjs';
6
- import { AzureServerSelector } from '../serverSelector/azure.mjs';
6
+ import { ServerSelector } from "../serverSelector.mjs";
7
7
  import { AiRawProviders } from './rawProviders.mjs';
8
8
  export class AiCustomProviders extends AiBase {
9
9
  oaiOpenai(args) {
10
10
  return new AiRawProviders(this.config).oaiOpenai(args);
11
11
  }
12
- async azOpenai(args, [server, ...servers] = new AzureServerSelector(this.config).closestServers()) {
12
+ async azOpenai(args, filteredServers) {
13
+ if (!filteredServers)
14
+ filteredServers = await new ServerSelector(this.config).closestServers(await import('@chainfuse/types/ai-tools/catalog/azure').then(({ azureCatalog }) => azureCatalog));
15
+ const [server, ...servers] = filteredServers;
13
16
  const raw = new AiRawProviders(this.config);
14
17
  return customProvider({
15
18
  // @ts-expect-error override for types
16
19
  languageModels: await server.languageModelAvailability.reduce(async (accPromise, model) => {
17
20
  const acc = await accPromise;
18
21
  // @ts-expect-error override for types
19
- acc[model] = wrapLanguageModel({
20
- model: (await raw.azOpenai(args, server))(model),
22
+ acc[model.name] = wrapLanguageModel({
23
+ model: (await raw.azOpenai(args, server, 'inputTokenCost' in model || 'outputTokenCost' in model ? { inputTokenCost: model.inputTokenCost, outputTokenCost: model.outputTokenCost } : undefined))(model.name),
21
24
  middleware: {
22
25
  wrapGenerate: async ({ doGenerate, model, params }) => {
23
26
  try {
@@ -28,23 +31,34 @@ export class AiCustomProviders extends AiBase {
28
31
  if (APICallError.isInstance(error)) {
29
32
  const idempotencyId = new Headers(error.responseHeaders).get('X-Idempotency-Id');
30
33
  const lastServer = new URL(error.url).pathname.split('/')[5];
31
- const compatibleServers = new AzureServerSelector(this.config).closestServers(model.modelId);
34
+ const compatibleServers = await new ServerSelector(this.config).closestServers(await import('@chainfuse/types/ai-tools/catalog/azure').then(({ azureCatalog }) => azureCatalog), model.modelId);
32
35
  const lastServerIndex = compatibleServers.findIndex((s) => s.id.toLowerCase() === lastServer.toLowerCase());
33
- if (args.logging ?? this.config.environment !== 'production')
36
+ if (args.logging ?? !this.config.environment.startsWith('production'))
34
37
  console.error('ai', 'custom provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), this.chalk.red('FAIL'), compatibleServers[lastServerIndex].id, 'REMAINING', JSON.stringify(compatibleServers.slice(lastServerIndex + 1).map((s) => s.id)));
35
38
  // Should retry with the next server
36
39
  const leftOverServers = compatibleServers.slice(lastServerIndex + 1);
37
40
  const errors = [error];
38
41
  for (const nextServer of leftOverServers) {
39
42
  try {
40
- if (args.logging ?? this.config.environment !== 'production')
43
+ if (args.logging ?? !this.config.environment.startsWith('production'))
41
44
  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
45
  // Must be double awaited to prevent a promise from being returned
43
- return await (await raw.azOpenai({ ...args, idempotencyId }, nextServer))(model.modelId).doGenerate(params);
46
+ return await (await raw.azOpenai({ ...args, idempotencyId }, nextServer, (() => {
47
+ const foundModel = server.languageModelAvailability.find((languageModel) => languageModel.name === model.modelId);
48
+ if (foundModel && ('inputTokenCost' in foundModel || 'outputTokenCost' in foundModel)) {
49
+ return {
50
+ inputTokenCost: foundModel.inputTokenCost,
51
+ outputTokenCost: foundModel.outputTokenCost,
52
+ };
53
+ }
54
+ else {
55
+ return undefined;
56
+ }
57
+ })()))(model.modelId).doGenerate(params);
44
58
  }
45
59
  catch (nextServerError) {
46
60
  if (APICallError.isInstance(nextServerError)) {
47
- if (args.logging ?? this.config.environment !== 'production')
61
+ if (args.logging ?? !this.config.environment.startsWith('production'))
48
62
  console.error('ai', 'custom provider', this.chalk.rgb(...Helpers.uniqueIdColor(idempotencyId))(`[${idempotencyId}]`), this.chalk.red('FAIL'), nextServer.id, 'REMAINING', JSON.stringify(leftOverServers.slice(leftOverServers.indexOf(nextServer) + 1).map((s) => s.id)));
49
63
  errors.push(nextServerError);
50
64
  }
@@ -1,14 +1,16 @@
1
1
  import type { OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
2
2
  import type { cloudflareModelPossibilities } from '@chainfuse/types';
3
3
  import { AiBase } from '../base.mjs';
4
- import type { Server } from '../serverSelector/types.mjs';
5
- import type { AiRequestConfig } from '../types.mjs';
4
+ import type { AiRequestConfig, Servers } from '../types.mjs';
6
5
  import type { WorkersAIProvider } from './types.mts';
7
6
  export declare class AiRawProviders extends AiBase {
8
7
  private readonly cacheTtl;
9
8
  private updateGatewayLog;
10
9
  oaiOpenai(args: AiRequestConfig): Promise<import("@ai-sdk/openai").OpenAIProvider>;
11
- azOpenai(args: AiRequestConfig, server: Server): Promise<import("@ai-sdk/azure").AzureOpenAIProvider>;
10
+ azOpenai(args: AiRequestConfig, server: Servers[number], cost?: {
11
+ inputTokenCost?: number;
12
+ outputTokenCost?: number;
13
+ }): Promise<import("@ai-sdk/azure").AzureOpenAIProvider>;
12
14
  anthropic(args: AiRequestConfig): Promise<import("@ai-sdk/anthropic").AnthropicProvider>;
13
15
  custom(args: AiRequestConfig): Promise<OpenAICompatibleProvider<string, string, string>>;
14
16
  googleAi(args: AiRequestConfig): Promise<import("@ai-sdk/google").GoogleGenerativeAIProvider>;
@@ -1,23 +1,28 @@
1
- import { BufferHelpers, CryptoHelpers, DnsHelpers, Helpers, NetHelpers } from '@chainfuse/helpers';
2
- import haversine from 'haversine-distance';
3
- import { z } from 'zod';
1
+ import { BufferHelpers, CryptoHelpers, Helpers } from '@chainfuse/helpers';
4
2
  import { AiBase } from '../base.mjs';
5
3
  export class AiRawProviders extends AiBase {
6
4
  // 2628288 seconds is what cf defines as 1 month in their cache rules
7
5
  cacheTtl = 2628288;
8
6
  async updateGatewayLog(response, metadataHeader, startRoundTrip, modelTime) {
9
- const updateMetadata = NetHelpers.cfApi(this.config.gateway.apiToken).then((cf) => cf.aiGateway.logs.edit(this.config.environment, response.headers.get('cf-aig-log-id'), {
7
+ const updateMetadata = import('@chainfuse/helpers')
8
+ .then(({ NetHelpers }) => NetHelpers.cfApi(this.config.gateway.apiToken))
9
+ .then((cf) => cf.aiGateway.logs.edit(this.config.environment, response.headers.get('cf-aig-log-id'), {
10
10
  account_id: this.config.gateway.accountId,
11
11
  metadata: {
12
- ...Object.entries(metadataHeader).reduce((acc, [key, value]) => {
12
+ ...Object.entries({
13
+ ...metadataHeader,
14
+ serverInfo: {
15
+ ...JSON.parse(metadataHeader.serverInfo),
16
+ timing: {
17
+ fromCache: response.headers.get('cf-aig-cache-status')?.toLowerCase() === 'hit',
18
+ totalRoundtripTime: performance.now() - startRoundTrip,
19
+ modelTime,
20
+ },
21
+ },
22
+ }).reduce((acc, [key, value]) => {
13
23
  acc[key] = typeof value === 'string' ? value : JSON.stringify(value);
14
24
  return acc;
15
25
  }, {}),
16
- timing: JSON.stringify({
17
- fromCache: response.headers.get('cf-aig-cache-status')?.toLowerCase() === 'hit',
18
- totalRoundtripTime: performance.now() - startRoundTrip,
19
- modelTime,
20
- }),
21
26
  },
22
27
  }));
23
28
  if (this.config.backgroundContext) {
@@ -35,21 +40,14 @@ export class AiRawProviders extends AiBase {
35
40
  headers: {
36
41
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
37
42
  'cf-aig-metadata': JSON.stringify({
38
- dbInfo: JSON.stringify({
39
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
40
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
41
- }),
42
- executor: JSON.stringify(args.executor),
43
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
44
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
43
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
44
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
45
45
  serverInfo: JSON.stringify({
46
46
  name: 'openai',
47
47
  }),
48
- /**
49
- * Blank at first, add after request finishes
50
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
51
- */
52
- timing: JSON.stringify({}),
48
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
49
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
50
+ executor: JSON.stringify(args.executor),
53
51
  }),
54
52
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
55
53
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
@@ -63,10 +61,10 @@ export class AiRawProviders extends AiBase {
63
61
  metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
64
62
  headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
65
63
  }
66
- if (args.logging ?? this.config.environment !== 'production')
64
+ if (args.logging ?? !this.config.environment.startsWith('production'))
67
65
  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));
68
66
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
69
- if (args.logging ?? this.config.environment !== 'production')
67
+ if (args.logging ?? !this.config.environment.startsWith('production'))
70
68
  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));
71
69
  await this.updateGatewayLog(response, metadataHeader, startRoundTrip, response.headers.has('openai-processing-ms') ? parseInt(response.headers.get('openai-processing-ms')) : undefined);
72
70
  // Inject it to have it available for retries
@@ -84,7 +82,7 @@ export class AiRawProviders extends AiBase {
84
82
  },
85
83
  }));
86
84
  }
87
- azOpenai(args, server) {
85
+ azOpenai(args, server, cost) {
88
86
  return import('@ai-sdk/azure').then(async ({ createAzure }) => createAzure({
89
87
  apiKey: this.config.providers.azureOpenAi.apiTokens[`AZURE_API_KEY_${server.id.toUpperCase().replaceAll('-', '_')}`],
90
88
  /**
@@ -95,26 +93,23 @@ export class AiRawProviders extends AiBase {
95
93
  baseURL: new URL(['v1', this.config.gateway.accountId, this.config.environment, 'azure-openai', server.id.toLowerCase()].join('/'), 'https://gateway.ai.cloudflare.com').toString(),
96
94
  headers: {
97
95
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
96
+ ...(cost && { 'cf-aig-custom-cost': JSON.stringify({ per_token_in: cost.inputTokenCost ?? undefined, per_token_out: cost.outputTokenCost ?? undefined }) }),
98
97
  'cf-aig-metadata': JSON.stringify({
99
- dbInfo: JSON.stringify({
100
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
101
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
102
- }),
103
- executor: JSON.stringify(args.executor),
104
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
105
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
98
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
99
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
106
100
  serverInfo: JSON.stringify({
107
101
  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),
102
+ distance: await import('haversine-distance').then(async ({ default: haversine }) => haversine(await import("../serverSelector.mjs").then(({ ServerSelector }) => new ServerSelector(this.config).determineLocation().then(({ coordinate }) => ({
103
+ lat: Helpers.precisionFloat(coordinate.lat),
104
+ lon: Helpers.precisionFloat(coordinate.lon),
105
+ }))), {
106
+ lat: Helpers.precisionFloat(server.coordinate.lat),
107
+ lon: Helpers.precisionFloat(server.coordinate.lon),
108
+ })),
112
109
  }),
113
- /**
114
- * Blank at first, add after request finishes
115
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
116
- */
117
- timing: JSON.stringify({}),
110
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
111
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
112
+ executor: JSON.stringify(args.executor),
118
113
  }),
119
114
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
120
115
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
@@ -127,10 +122,10 @@ export class AiRawProviders extends AiBase {
127
122
  metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
128
123
  headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
129
124
  }
130
- if (args.logging ?? this.config.environment !== 'production')
125
+ if (args.logging ?? !this.config.environment.startsWith('production'))
131
126
  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));
132
127
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
133
- if (args.logging ?? this.config.environment !== 'production')
128
+ if (args.logging ?? !this.config.environment.startsWith('production'))
134
129
  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
130
  await this.updateGatewayLog(response, metadataHeader, startRoundTrip, response.headers.has('x-envoy-upstream-service-time') ? parseInt(response.headers.get('x-envoy-upstream-service-time')) : undefined);
136
131
  // Inject it to have it available for retries
@@ -155,21 +150,14 @@ export class AiRawProviders extends AiBase {
155
150
  headers: {
156
151
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
157
152
  'cf-aig-metadata': JSON.stringify({
158
- dbInfo: JSON.stringify({
159
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
160
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
161
- }),
162
- executor: JSON.stringify(args.executor),
163
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
164
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
153
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
154
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
165
155
  serverInfo: JSON.stringify({
166
156
  name: 'anthropic',
167
157
  }),
168
- /**
169
- * Blank at first, add after request finishes
170
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
171
- */
172
- timing: JSON.stringify({}),
158
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
159
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
160
+ executor: JSON.stringify(args.executor),
173
161
  }),
174
162
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
175
163
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
@@ -182,10 +170,10 @@ export class AiRawProviders extends AiBase {
182
170
  metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
183
171
  headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
184
172
  }
185
- if (args.logging ?? this.config.environment !== 'production')
173
+ if (args.logging ?? !this.config.environment.startsWith('production'))
186
174
  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));
187
175
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
188
- if (args.logging ?? this.config.environment !== 'production')
176
+ if (args.logging ?? !this.config.environment.startsWith('production'))
189
177
  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));
190
178
  await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
191
179
  // Inject it to have it available for retries
@@ -205,8 +193,10 @@ export class AiRawProviders extends AiBase {
205
193
  }
206
194
  custom(args) {
207
195
  if (this.config.providers.custom?.url) {
196
+ return import('zod')
197
+ .then(({ z }) =>
208
198
  // Verify that the custom provider url is a valid URL
209
- return z
199
+ z
210
200
  .string()
211
201
  .trim()
212
202
  .url()
@@ -219,13 +209,14 @@ export class AiRawProviders extends AiBase {
219
209
  .trim()
220
210
  .ip()
221
211
  .safeParseAsync(customProviderUrl.hostname)
222
- .then(async ({ success }) => {
212
+ .then(({ success }) => ({ customProviderUrl, success }))))
213
+ .then(async ({ customProviderUrl, success }) => {
223
214
  if (success) {
224
215
  throw new Error('IP custom providers not allowed');
225
216
  }
226
217
  else {
227
218
  // Run domain through ZT policies
228
- const doh = new DnsHelpers(new URL('dns-query', `https://${this.config.providers.custom?.dohId}.cloudflare-gateway.com`));
219
+ const doh = await import('@chainfuse/helpers').then(({ DnsHelpers }) => new DnsHelpers(new URL('dns-query', `https://${this.config.providers.custom?.dohId}.cloudflare-gateway.com`)));
229
220
  const aCheck = doh.query(customProviderUrl.hostname, 'A', undefined, undefined, 2 * 1000);
230
221
  const aaaaCheck = doh.query(customProviderUrl.hostname, 'AAAA', undefined, undefined, 2 * 1000);
231
222
  return Promise.allSettled([aCheck, aaaaCheck]).then((checks) => {
@@ -265,10 +256,10 @@ export class AiRawProviders extends AiBase {
265
256
  idempotencyId = `${idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
266
257
  headers.set('X-Idempotency-Id', idempotencyId);
267
258
  }
268
- if (args.logging ?? this.config.environment !== 'production')
259
+ if (args.logging ?? !this.config.environment.startsWith('production'))
269
260
  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));
270
261
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
271
- if (args.logging ?? this.config.environment !== 'production')
262
+ if (args.logging ?? !this.config.environment.startsWith('production'))
272
263
  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));
273
264
  // Inject it to have it available for retries
274
265
  const mutableHeaders = new Headers(response.headers);
@@ -290,7 +281,7 @@ export class AiRawProviders extends AiBase {
290
281
  }
291
282
  });
292
283
  }
293
- }))
284
+ })
294
285
  .catch(() => {
295
286
  throw new Error('Invalid custom provider url');
296
287
  });
@@ -319,21 +310,14 @@ export class AiRawProviders extends AiBase {
319
310
  headers: {
320
311
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
321
312
  'cf-aig-metadata': JSON.stringify({
322
- dbInfo: JSON.stringify({
323
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
324
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
325
- }),
326
- executor: JSON.stringify(args.executor),
327
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
328
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
313
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
314
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
329
315
  serverInfo: JSON.stringify({
330
316
  name: 'googleai',
331
317
  }),
332
- /**
333
- * Blank at first, add after request finishes
334
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
335
- */
336
- timing: JSON.stringify({}),
318
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
319
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
320
+ executor: JSON.stringify(args.executor),
337
321
  }),
338
322
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
339
323
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
@@ -346,10 +330,10 @@ export class AiRawProviders extends AiBase {
346
330
  metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
347
331
  headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
348
332
  }
349
- if (args.logging ?? this.config.environment !== 'production')
333
+ if (args.logging ?? !this.config.environment.startsWith('production'))
350
334
  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));
351
335
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
352
- if (args.logging ?? this.config.environment !== 'production')
336
+ if (args.logging ?? !this.config.environment.startsWith('production'))
353
337
  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));
354
338
  await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
355
339
  // Inject it to have it available for retries
@@ -374,21 +358,14 @@ export class AiRawProviders extends AiBase {
374
358
  headers: {
375
359
  'cf-aig-authorization': `Bearer ${this.config.gateway.apiToken}`,
376
360
  'cf-aig-metadata': JSON.stringify({
377
- dbInfo: JSON.stringify({
378
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
379
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
380
- }),
381
- executor: JSON.stringify(args.executor),
382
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
383
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
361
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
362
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
384
363
  serverInfo: JSON.stringify({
385
364
  name: 'cloudflare',
386
365
  }),
387
- /**
388
- * Blank at first, add after request finishes
389
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
390
- */
391
- timing: JSON.stringify({}),
366
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
367
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
368
+ executor: JSON.stringify(args.executor),
392
369
  }),
393
370
  ...(args.cache && { 'cf-aig-cache-ttl': (typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache).toString() }),
394
371
  ...(args.skipCache && { 'cf-aig-skip-cache': 'true' }),
@@ -402,10 +379,10 @@ export class AiRawProviders extends AiBase {
402
379
  metadataHeader.idempotencyId = `${metadataHeader.idempotencyId}-${(await CryptoHelpers.getHash('SHA-256', await new Request(input, rawInit).arrayBuffer())).slice(0, 12)}`;
403
380
  headers.set('cf-aig-metadata', JSON.stringify(metadataHeader));
404
381
  }
405
- if (args.logging ?? this.config.environment !== 'production')
382
+ if (args.logging ?? !this.config.environment.startsWith('production'))
406
383
  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));
407
384
  return fetch(input, { ...rawInit, headers }).then(async (response) => {
408
- if (args.logging ?? this.config.environment !== 'production')
385
+ if (args.logging ?? !this.config.environment.startsWith('production'))
409
386
  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));
410
387
  await this.updateGatewayLog(response, metadataHeader, startRoundTrip);
411
388
  // Inject it to have it available for retries
@@ -431,21 +408,14 @@ export class AiRawProviders extends AiBase {
431
408
  ...(args.cache && { cacheTtl: typeof args.cache === 'boolean' ? (args.cache ? this.cacheTtl : 0) : args.cache }),
432
409
  ...(args.skipCache && { skipCache: true }),
433
410
  metadata: {
434
- dbInfo: JSON.stringify({
435
- messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
436
- dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
437
- }),
438
- executor: JSON.stringify(args.executor),
439
- // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
440
- idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
411
+ dataspaceId: (await BufferHelpers.uuidConvert(args.dataspaceId)).utf8,
412
+ messageId: (await BufferHelpers.uuidConvert(args.messageId)).utf8,
441
413
  serverInfo: JSON.stringify({
442
414
  name: 'cloudflare',
443
415
  }),
444
- /**
445
- * Blank at first, add after request finishes
446
- * CF AI Gateway allows only editing existing metadata not creating new ones after the request is made
447
- */
448
- timing: JSON.stringify({}),
416
+ // Generate incomplete id because we don't have the body to hash yet. Fill it in in the `fetch()`
417
+ idempotencyId: args.idempotencyId ?? (await BufferHelpers.generateUuid).utf8.slice(0, 23),
418
+ executor: JSON.stringify(args.executor),
449
419
  },
450
420
  },
451
421
  }));
package/dist/registry.mjs CHANGED
@@ -1,18 +1,16 @@
1
- import { experimental_createProviderRegistry as createProviderRegistry } from 'ai';
2
1
  import { AiBase } from './base.mjs';
3
- import { AiCustomProviders } from './providers/customProviders.mjs';
4
2
  export class AiRegistry extends AiBase {
5
- async providers(args) {
6
- return Object.freeze({
3
+ providers(args) {
4
+ return import('./providers/customProviders.mjs').then(async ({ AiCustomProviders }) => Object.freeze({
7
5
  openai: await new AiCustomProviders(this.config).oaiOpenai(args),
8
6
  azure: await new AiCustomProviders(this.config).azOpenai(args),
9
7
  anthropic: await new AiCustomProviders(this.config).anthropic(args),
10
8
  custom: await new AiCustomProviders(this.config).custom(args),
11
9
  'google.generative-ai': await new AiCustomProviders(this.config).googleAi(args),
12
10
  workersai: await new AiCustomProviders(this.config).cfWorkersAi(args),
13
- });
11
+ }));
14
12
  }
15
- async registry(args) {
16
- return createProviderRegistry(await this.providers(args));
13
+ registry(args) {
14
+ return import('ai').then(async ({ experimental_createProviderRegistry: createProviderRegistry }) => createProviderRegistry(await this.providers(args)));
17
15
  }
18
16
  }
@@ -0,0 +1,17 @@
1
+ import type { Coordinate } from '@chainfuse/types';
2
+ import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
3
+ import { AiBase } from './base.mts';
4
+ import type { PrivacyRegion, Servers } from './types.mjs';
5
+ export declare class ServerSelector extends AiBase {
6
+ static determinePrivacyRegion(country?: IncomingRequestCfProperties['country'], continent?: IncomingRequestCfProperties['continent']): ("APPs" | "LGPD" | "GDPR" | "PIPEDA" | "APPI" | "UK-GDPR" | "PIPA" | "PoPIA" | "revFADP" | "NPDA" | "PDP")[];
7
+ determineLocation(geoRouting?: {
8
+ userCoordinate?: Coordinate;
9
+ country?: IncomingRequestCfProperties["country"];
10
+ continent?: IncomingRequestCfProperties["continent"];
11
+ } | undefined): Promise<{
12
+ coordinate: Coordinate;
13
+ country: IncomingRequestCfProperties['country'];
14
+ continent: IncomingRequestCfProperties['continent'];
15
+ }>;
16
+ closestServers(servers: Servers, requiredCapability?: string, userCoordinate?: Coordinate, privacyRegion?: PrivacyRegion[]): Promise<Servers>;
17
+ }