@tanstack/ai-cohere 0.3.3 → 0.4.1

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
@@ -1,5 +1,19 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/TanStack/ai/main/media/header_ai.png" alt="TanStack AI" />
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/ai.png"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/ai.png"
13
+ alt="TanStack AI"
14
+ width="900"
15
+ />
16
+ </picture>
3
17
  </div>
4
18
 
5
19
  <br />
@@ -1,10 +1,9 @@
1
- import { getCohereApiKeyFromEnv } from "../utils/client.js";
1
+ import { getCohereApiKeyFromEnv, resolveCohereTransport } from "../utils/client.js";
2
2
  import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters";
3
3
  import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
4
4
  import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
5
5
  import { resolveEmbeddingInput } from "@tanstack/ai";
6
6
  //#region src/adapters/embedding.ts
7
- var DEFAULT_BASE_URL = "https://api.cohere.com";
8
7
  var DEFAULT_TIMEOUT_MS = 3e4;
9
8
  /**
10
9
  * Returns true when `url` is malformed, non-http(s), or targets a private /
@@ -86,12 +85,13 @@ var CohereEmbeddingAdapter = class extends BaseEmbeddingAdapter {
86
85
  model
87
86
  });
88
87
  const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS;
89
- const response = await fetchWithTimeout(`${this.clientConfig.baseUrl ?? DEFAULT_BASE_URL}/v2/embed`, {
88
+ const transport = resolveCohereTransport(this.clientConfig);
89
+ const response = await fetchWithTimeout(`${transport.baseUrl}/v2/embed`, {
90
90
  method: "POST",
91
91
  headers: {
92
92
  Authorization: `Bearer ${this.clientConfig.apiKey}`,
93
93
  "Content-Type": "application/json",
94
- ...this.clientConfig.headers
94
+ ...transport.headers
95
95
  },
96
96
  body: JSON.stringify(body)
97
97
  }, timeoutMs);
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.js","names":[],"sources":["../../../src/adapters/embedding.ts"],"sourcesContent":["import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { resolveEmbeddingInput } from '@tanstack/ai'\nimport { getCohereApiKeyFromEnv } from '../utils/client'\nimport type {\n EmbeddingOptions,\n EmbeddingResult,\n ImagePart,\n TokenUsage,\n} from '@tanstack/ai'\nimport type {\n CohereEmbeddingModel,\n CohereEmbeddingModelInputModalitiesByName,\n CohereEmbeddingModelProviderOptionsByName,\n} from '../model-meta'\nimport type { CohereEmbeddingProviderOptions } from '../embedding/embedding-provider-options'\nimport type { CohereClientConfig } from '../utils/client'\n\n/**\n * Configuration for Cohere embedding adapter.\n */\nexport interface CohereEmbeddingConfig extends CohereClientConfig {}\n\nconst DEFAULT_BASE_URL = 'https://api.cohere.com'\nconst DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Returns true when `url` is malformed, non-http(s), or targets a private /\n * loopback / link-local host. Used to block SSRF via `allowUrlFetch`.\n */\nfunction isPrivateOrInternalUrl(url: string): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return true\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return true\n }\n const host = parsed.hostname.toLowerCase()\n if (\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n host === '::1' ||\n host === '[::1]' ||\n host.startsWith('127.') ||\n host.startsWith('10.') ||\n host.startsWith('192.168.') ||\n host.startsWith('169.254.') ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(host)\n ) {\n return true\n }\n return false\n}\n\nasync function fetchWithTimeout(\n url: string,\n init: RequestInit | undefined,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { ...init, signal: controller.signal })\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\n/** One content part of a Cohere v2/embed fused input. */\ntype CohereEmbedContentPart =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\n/** Wire shape of the Cohere v2/embed request body. */\ninterface CohereEmbedRequestBody {\n model: string\n inputs: Array<{ content: Array<CohereEmbedContentPart> }>\n input_type: CohereEmbeddingProviderOptions['inputType']\n embedding_types: ['float']\n truncate?: 'NONE' | 'START' | 'END'\n output_dimension?: number\n}\n\n/** Wire shape of the Cohere v2/embed response (fields the adapter reads). */\ninterface CohereEmbedResponse {\n id?: string\n embeddings?: {\n float?: Array<Array<number>>\n }\n meta?: {\n billed_units?: {\n input_tokens?: number\n images?: number\n }\n }\n}\n\n/**\n * Cohere Embedding Adapter\n *\n * Tree-shakeable adapter for Cohere multimodal embeddings (embed-v4.0),\n * implemented with plain `fetch` against the v2/embed endpoint — no Cohere\n * SDK dependency.\n *\n * Features:\n * - Batch embedding (one request for the whole input array)\n * - Multimodal inputs: text, images, and fused text+image items (one vector\n * per input item)\n * - Matryoshka dimension reduction via the top-level `dimensions` option\n * (mapped to Cohere's `output_dimension`)\n */\nexport class CohereEmbeddingAdapter<\n TModel extends CohereEmbeddingModel,\n> extends BaseEmbeddingAdapter<\n TModel,\n CohereEmbeddingProviderOptions,\n CohereEmbeddingModelProviderOptionsByName,\n CohereEmbeddingModelInputModalitiesByName\n> {\n readonly name = 'cohere' as const\n\n protected clientConfig: CohereEmbeddingConfig\n\n constructor(config: CohereEmbeddingConfig, model: TModel) {\n super(model, {})\n this.clientConfig = config\n }\n\n async createEmbeddings(\n options: EmbeddingOptions<CohereEmbeddingProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, logger, modelOptions } = options\n\n try {\n // The provider options type makes `modelOptions` required at the\n // embed() call site; this guard covers untyped/dynamic callers.\n const inputType: CohereEmbeddingProviderOptions['inputType'] | undefined =\n modelOptions?.inputType\n if (!inputType) {\n throw new Error(\n `Cohere embeddings require modelOptions.inputType ('search_document' | 'search_query' | 'classification' | 'clustering').`,\n )\n }\n\n const resolved = resolveEmbeddingInput(options.input)\n const inputs = await Promise.all(\n resolved.map(async (item) => {\n const content: Array<CohereEmbedContentPart> = item.texts.map(\n (text) => ({ type: 'text', text }),\n )\n for (const image of item.images) {\n content.push({\n type: 'image_url',\n image_url: { url: await this.resolveImageUrl(image) },\n })\n }\n return { content }\n }),\n )\n\n // embedding_types is pinned to ['float'] (overriding any disagreeing\n // modelOptions.embeddingTypes) so vectors are always number[].\n const body: CohereEmbedRequestBody = {\n model,\n inputs,\n input_type: inputType,\n embedding_types: ['float'],\n }\n const truncate = modelOptions?.truncate\n if (truncate !== undefined) {\n body.truncate = truncate\n }\n if (options.dimensions !== undefined) {\n body.output_dimension = options.dimensions\n }\n\n logger.request(\n `activity=embed provider=${this.name} model=${model} inputs=${inputs.length}`,\n { provider: this.name, model },\n )\n\n const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS\n const response = await fetchWithTimeout(\n `${this.clientConfig.baseUrl ?? DEFAULT_BASE_URL}/v2/embed`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.clientConfig.apiKey}`,\n 'Content-Type': 'application/json',\n ...this.clientConfig.headers,\n },\n body: JSON.stringify(body),\n },\n timeoutMs,\n )\n\n if (!response.ok) {\n const bodyText = await response.text()\n let message = bodyText\n try {\n const parsed: unknown = JSON.parse(bodyText)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'message' in parsed &&\n typeof parsed.message === 'string'\n ) {\n message = parsed.message\n }\n } catch {\n // Not JSON — fall back to the raw body text.\n }\n throw new Error(`Cohere embed failed (${response.status}): ${message}`)\n }\n\n const data = (await response.json()) as CohereEmbedResponse\n\n const vectors = data.embeddings?.float\n if (!vectors) {\n throw new Error(\n 'Cohere embed response did not include float embeddings',\n )\n }\n if (vectors.length !== inputs.length) {\n throw new Error(\n `Cohere embed returned ${vectors.length} embeddings for ${inputs.length} inputs`,\n )\n }\n\n const result: EmbeddingResult = {\n id: generateId(this.name),\n model,\n embeddings: vectors.map((vector, index) => ({ vector, index })),\n }\n\n const inputTokens = data.meta?.billed_units?.input_tokens\n if (inputTokens !== undefined) {\n const usage: TokenUsage = {\n promptTokens: inputTokens,\n completionTokens: 0,\n totalTokens: inputTokens,\n }\n result.usage = usage\n }\n\n return result\n } catch (error: unknown) {\n logger.errors(`${this.name}.createEmbeddings fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),\n source: `${this.name}.createEmbeddings`,\n })\n throw error\n }\n }\n\n /**\n * Resolves an image part to a URL Cohere accepts. Cohere does not fetch\n * remote image URLs, so everything is normalized to a `data:` URI unless\n * the caller already provided one.\n */\n protected async resolveImageUrl(image: ImagePart): Promise<string> {\n const source = image.source\n\n if (source.type === 'data') {\n return `data:${source.mimeType};base64,${source.value}`\n }\n\n if (source.value.startsWith('data:')) {\n return source.value\n }\n\n if (!this.clientConfig.allowUrlFetch) {\n throw new Error(\n 'Cohere does not fetch remote image URLs; pass base64 data or a data: URI (or enable config.allowUrlFetch to have the adapter download it)',\n )\n }\n\n if (isPrivateOrInternalUrl(source.value)) {\n throw new Error(\n `Refusing to fetch internal or private URL for Cohere embedding: ${source.value}`,\n )\n }\n\n const response = await fetchWithTimeout(\n source.value,\n undefined,\n this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS,\n )\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image URL for Cohere embedding (${response.status}): ${source.value}`,\n )\n }\n const mimeType =\n response.headers.get('content-type') ??\n source.mimeType ??\n 'application/octet-stream'\n const base64 = arrayBufferToBase64(await response.arrayBuffer())\n return `data:${mimeType};base64,${base64}`\n }\n}\n\n/**\n * Creates a Cohere embedding adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'embed-v4.0')\n * @param apiKey - Your Cohere API key\n * @param config - Optional additional configuration\n * @returns Configured Cohere embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createCohereEmbedding('embed-v4.0', 'api_key');\n *\n * const result = await embed({\n * adapter,\n * input: 'a red guitar',\n * modelOptions: { inputType: 'search_document' }\n * });\n * ```\n */\nexport function createCohereEmbedding<TModel extends CohereEmbeddingModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<CohereEmbeddingConfig, 'apiKey'>,\n): CohereEmbeddingAdapter<TModel> {\n return new CohereEmbeddingAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Cohere embedding adapter using the `COHERE_API_KEY` environment variable.\n * Type resolution happens here at the call site.\n *\n * Looks for `COHERE_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'embed-v4.0')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Cohere embedding adapter instance with resolved types\n * @throws Error if COHERE_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses COHERE_API_KEY from environment\n * const adapter = cohereEmbedding('embed-v4.0');\n *\n * const result = await embed({\n * adapter,\n * input: ['a red guitar', 'a blue drum kit'],\n * modelOptions: { inputType: 'search_query' },\n * dimensions: 1024\n * });\n *\n * console.log(result.embeddings[0].vector)\n * ```\n */\nexport function cohereEmbedding<TModel extends CohereEmbeddingModel>(\n model: TModel,\n config?: Omit<CohereEmbeddingConfig, 'apiKey'>,\n): CohereEmbeddingAdapter<TModel> {\n const apiKey = getCohereApiKeyFromEnv()\n return createCohereEmbedding(model, apiKey, config)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"embedding.js","names":[],"sources":["../../../src/adapters/embedding.ts"],"sourcesContent":["import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { resolveEmbeddingInput } from '@tanstack/ai'\nimport { getCohereApiKeyFromEnv, resolveCohereTransport } from '../utils/client'\nimport type {\n EmbeddingOptions,\n EmbeddingResult,\n ImagePart,\n TokenUsage,\n} from '@tanstack/ai'\nimport type {\n CohereEmbeddingModel,\n CohereEmbeddingModelInputModalitiesByName,\n CohereEmbeddingModelProviderOptionsByName,\n} from '../model-meta'\nimport type { CohereEmbeddingProviderOptions } from '../embedding/embedding-provider-options'\nimport type { CohereClientConfig } from '../utils/client'\n\n/**\n * Configuration for Cohere embedding adapter.\n */\nexport interface CohereEmbeddingConfig extends CohereClientConfig {}\n\nconst DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Returns true when `url` is malformed, non-http(s), or targets a private /\n * loopback / link-local host. Used to block SSRF via `allowUrlFetch`.\n */\nfunction isPrivateOrInternalUrl(url: string): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return true\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return true\n }\n const host = parsed.hostname.toLowerCase()\n if (\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n host === '::1' ||\n host === '[::1]' ||\n host.startsWith('127.') ||\n host.startsWith('10.') ||\n host.startsWith('192.168.') ||\n host.startsWith('169.254.') ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(host)\n ) {\n return true\n }\n return false\n}\n\nasync function fetchWithTimeout(\n url: string,\n init: RequestInit | undefined,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { ...init, signal: controller.signal })\n } finally {\n clearTimeout(timeoutId)\n }\n}\n\n/** One content part of a Cohere v2/embed fused input. */\ntype CohereEmbedContentPart =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\n/** Wire shape of the Cohere v2/embed request body. */\ninterface CohereEmbedRequestBody {\n model: string\n inputs: Array<{ content: Array<CohereEmbedContentPart> }>\n input_type: CohereEmbeddingProviderOptions['inputType']\n embedding_types: ['float']\n truncate?: 'NONE' | 'START' | 'END'\n output_dimension?: number\n}\n\n/** Wire shape of the Cohere v2/embed response (fields the adapter reads). */\ninterface CohereEmbedResponse {\n id?: string\n embeddings?: {\n float?: Array<Array<number>>\n }\n meta?: {\n billed_units?: {\n input_tokens?: number\n images?: number\n }\n }\n}\n\n/**\n * Cohere Embedding Adapter\n *\n * Tree-shakeable adapter for Cohere multimodal embeddings (embed-v4.0),\n * implemented with plain `fetch` against the v2/embed endpoint — no Cohere\n * SDK dependency.\n *\n * Features:\n * - Batch embedding (one request for the whole input array)\n * - Multimodal inputs: text, images, and fused text+image items (one vector\n * per input item)\n * - Matryoshka dimension reduction via the top-level `dimensions` option\n * (mapped to Cohere's `output_dimension`)\n */\nexport class CohereEmbeddingAdapter<\n TModel extends CohereEmbeddingModel,\n> extends BaseEmbeddingAdapter<\n TModel,\n CohereEmbeddingProviderOptions,\n CohereEmbeddingModelProviderOptionsByName,\n CohereEmbeddingModelInputModalitiesByName\n> {\n readonly name = 'cohere' as const\n\n protected clientConfig: CohereEmbeddingConfig\n\n constructor(config: CohereEmbeddingConfig, model: TModel) {\n super(model, {})\n this.clientConfig = config\n }\n\n async createEmbeddings(\n options: EmbeddingOptions<CohereEmbeddingProviderOptions>,\n ): Promise<EmbeddingResult> {\n const { model, logger, modelOptions } = options\n\n try {\n // The provider options type makes `modelOptions` required at the\n // embed() call site; this guard covers untyped/dynamic callers.\n const inputType: CohereEmbeddingProviderOptions['inputType'] | undefined =\n modelOptions?.inputType\n if (!inputType) {\n throw new Error(\n `Cohere embeddings require modelOptions.inputType ('search_document' | 'search_query' | 'classification' | 'clustering').`,\n )\n }\n\n const resolved = resolveEmbeddingInput(options.input)\n const inputs = await Promise.all(\n resolved.map(async (item) => {\n const content: Array<CohereEmbedContentPart> = item.texts.map(\n (text) => ({ type: 'text', text }),\n )\n for (const image of item.images) {\n content.push({\n type: 'image_url',\n image_url: { url: await this.resolveImageUrl(image) },\n })\n }\n return { content }\n }),\n )\n\n // embedding_types is pinned to ['float'] (overriding any disagreeing\n // modelOptions.embeddingTypes) so vectors are always number[].\n const body: CohereEmbedRequestBody = {\n model,\n inputs,\n input_type: inputType,\n embedding_types: ['float'],\n }\n const truncate = modelOptions?.truncate\n if (truncate !== undefined) {\n body.truncate = truncate\n }\n if (options.dimensions !== undefined) {\n body.output_dimension = options.dimensions\n }\n\n logger.request(\n `activity=embed provider=${this.name} model=${model} inputs=${inputs.length}`,\n { provider: this.name, model },\n )\n\n const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS\n const transport = resolveCohereTransport(this.clientConfig)\n const response = await fetchWithTimeout(\n `${transport.baseUrl}/v2/embed`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.clientConfig.apiKey}`,\n 'Content-Type': 'application/json',\n ...transport.headers,\n },\n body: JSON.stringify(body),\n },\n timeoutMs,\n )\n\n if (!response.ok) {\n const bodyText = await response.text()\n let message = bodyText\n try {\n const parsed: unknown = JSON.parse(bodyText)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'message' in parsed &&\n typeof parsed.message === 'string'\n ) {\n message = parsed.message\n }\n } catch {\n // Not JSON — fall back to the raw body text.\n }\n throw new Error(`Cohere embed failed (${response.status}): ${message}`)\n }\n\n const data = (await response.json()) as CohereEmbedResponse\n\n const vectors = data.embeddings?.float\n if (!vectors) {\n throw new Error(\n 'Cohere embed response did not include float embeddings',\n )\n }\n if (vectors.length !== inputs.length) {\n throw new Error(\n `Cohere embed returned ${vectors.length} embeddings for ${inputs.length} inputs`,\n )\n }\n\n const result: EmbeddingResult = {\n id: generateId(this.name),\n model,\n embeddings: vectors.map((vector, index) => ({ vector, index })),\n }\n\n const inputTokens = data.meta?.billed_units?.input_tokens\n if (inputTokens !== undefined) {\n const usage: TokenUsage = {\n promptTokens: inputTokens,\n completionTokens: 0,\n totalTokens: inputTokens,\n }\n result.usage = usage\n }\n\n return result\n } catch (error: unknown) {\n logger.errors(`${this.name}.createEmbeddings fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),\n source: `${this.name}.createEmbeddings`,\n })\n throw error\n }\n }\n\n /**\n * Resolves an image part to a URL Cohere accepts. Cohere does not fetch\n * remote image URLs, so everything is normalized to a `data:` URI unless\n * the caller already provided one.\n */\n protected async resolveImageUrl(image: ImagePart): Promise<string> {\n const source = image.source\n\n if (source.type === 'data') {\n return `data:${source.mimeType};base64,${source.value}`\n }\n\n if (source.value.startsWith('data:')) {\n return source.value\n }\n\n if (!this.clientConfig.allowUrlFetch) {\n throw new Error(\n 'Cohere does not fetch remote image URLs; pass base64 data or a data: URI (or enable config.allowUrlFetch to have the adapter download it)',\n )\n }\n\n if (isPrivateOrInternalUrl(source.value)) {\n throw new Error(\n `Refusing to fetch internal or private URL for Cohere embedding: ${source.value}`,\n )\n }\n\n const response = await fetchWithTimeout(\n source.value,\n undefined,\n this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS,\n )\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image URL for Cohere embedding (${response.status}): ${source.value}`,\n )\n }\n const mimeType =\n response.headers.get('content-type') ??\n source.mimeType ??\n 'application/octet-stream'\n const base64 = arrayBufferToBase64(await response.arrayBuffer())\n return `data:${mimeType};base64,${base64}`\n }\n}\n\n/**\n * Creates a Cohere embedding adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'embed-v4.0')\n * @param apiKey - Your Cohere API key\n * @param config - Optional additional configuration\n * @returns Configured Cohere embedding adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createCohereEmbedding('embed-v4.0', 'api_key');\n *\n * const result = await embed({\n * adapter,\n * input: 'a red guitar',\n * modelOptions: { inputType: 'search_document' }\n * });\n * ```\n */\nexport function createCohereEmbedding<TModel extends CohereEmbeddingModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<CohereEmbeddingConfig, 'apiKey'>,\n): CohereEmbeddingAdapter<TModel> {\n return new CohereEmbeddingAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Cohere embedding adapter using the `COHERE_API_KEY` environment variable.\n * Type resolution happens here at the call site.\n *\n * Looks for `COHERE_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'embed-v4.0')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Cohere embedding adapter instance with resolved types\n * @throws Error if COHERE_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses COHERE_API_KEY from environment\n * const adapter = cohereEmbedding('embed-v4.0');\n *\n * const result = await embed({\n * adapter,\n * input: ['a red guitar', 'a blue drum kit'],\n * modelOptions: { inputType: 'search_query' },\n * dimensions: 1024\n * });\n *\n * console.log(result.embeddings[0].vector)\n * ```\n */\nexport function cohereEmbedding<TModel extends CohereEmbeddingModel>(\n model: TModel,\n config?: Omit<CohereEmbeddingConfig, 'apiKey'>,\n): CohereEmbeddingAdapter<TModel> {\n const apiKey = getCohereApiKeyFromEnv()\n return createCohereEmbedding(model, apiKey, config)\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { getCohereApiKeyFromEnv } from "../utils/client.js";
1
+ import { getCohereApiKeyFromEnv, resolveCohereTransport } from "../utils/client.js";
2
2
  import { BaseRerankAdapter } from "@tanstack/ai/adapters";
3
3
  //#region src/adapters/rerank.ts
4
4
  function isCohereRerankResponse(value) {
@@ -21,8 +21,9 @@ var CohereRerankAdapter = class extends BaseRerankAdapter {
21
21
  constructor(config, model) {
22
22
  super({}, model);
23
23
  this.apiKey = config.apiKey;
24
- this.baseUrl = (config.baseUrl ?? "https://api.cohere.com").replace(/\/+$/, "");
25
- this.headers = config.headers ?? {};
24
+ const transport = resolveCohereTransport(config);
25
+ this.baseUrl = transport.baseUrl;
26
+ this.headers = transport.headers;
26
27
  }
27
28
  async rerank(options) {
28
29
  const { model, query, documents, topN, modelOptions, abortSignal, logger } = options;
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.js","names":[],"sources":["../../../src/adapters/rerank.ts"],"sourcesContent":["import { BaseRerankAdapter } from '@tanstack/ai/adapters'\nimport {\n COHERE_DEFAULT_BASE_URL,\n getCohereApiKeyFromEnv,\n} from '../utils/client'\nimport type { CohereClientConfig } from '../utils/client'\nimport type {\n CohereRerankModel,\n InferCohereRerankProviderOptions,\n} from '../model-meta'\nimport type {\n RerankAdapterResult,\n RerankOptions,\n TokenUsage,\n} from '@tanstack/ai'\n\n/** Shape of the Cohere `/v2/rerank` response we depend on. */\ninterface CohereRerankResponse {\n id?: string\n results: Array<{ index: number; relevance_score: number }>\n meta?: { billed_units?: { search_units?: number } }\n}\n\nfunction isCohereRerankResponse(value: unknown): value is CohereRerankResponse {\n if (typeof value !== 'object' || value === null) return false\n const results = (value as { results?: unknown }).results\n return (\n Array.isArray(results) &&\n results.every(\n (r) =>\n typeof r === 'object' &&\n r !== null &&\n typeof (r as { index?: unknown }).index === 'number' &&\n typeof (r as { relevance_score?: unknown }).relevance_score ===\n 'number',\n )\n )\n}\n\n/**\n * Cohere rerank adapter.\n *\n * Talks to Cohere's `/v2/rerank` endpoint over raw `fetch` — no SDK. Returns\n * scored indices into the submitted documents; the `rerank()` activity maps\n * those back to the caller's original documents.\n */\nexport class CohereRerankAdapter<\n TModel extends CohereRerankModel,\n> extends BaseRerankAdapter<TModel, InferCohereRerankProviderOptions<TModel>> {\n readonly name = 'cohere' as const\n\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly headers: Record<string, string>\n\n constructor(config: CohereClientConfig, model: TModel) {\n super({}, model)\n this.apiKey = config.apiKey\n this.baseUrl = (config.baseUrl ?? COHERE_DEFAULT_BASE_URL).replace(\n /\\/+$/,\n '',\n )\n this.headers = config.headers ?? {}\n }\n\n async rerank(\n options: RerankOptions<InferCohereRerankProviderOptions<TModel>>,\n ): Promise<RerankAdapterResult> {\n const { model, query, documents, topN, modelOptions, abortSignal, logger } =\n options\n\n const body: Record<string, unknown> = { model, query, documents }\n if (topN !== undefined) body['top_n'] = topN\n if (modelOptions?.maxTokensPerDoc !== undefined) {\n body['max_tokens_per_doc'] = modelOptions.maxTokensPerDoc\n }\n\n logger.request(\n `activity=rerank provider=${this.name} model=${model} documents=${documents.length}`,\n { provider: this.name, model },\n )\n\n let response: Response\n try {\n response = await fetch(`${this.baseUrl}/v2/rerank`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n ...this.headers,\n },\n body: JSON.stringify(body),\n ...(abortSignal ? { signal: abortSignal } : {}),\n })\n } catch (error) {\n logger.errors(`${this.name}.rerank fatal`, {\n error,\n source: `${this.name}.rerank`,\n })\n throw error\n }\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n const error = new Error(\n `Cohere rerank request failed: ${response.status} ${response.statusText}${\n detail ? ` — ${detail}` : ''\n }`,\n )\n logger.errors(`${this.name}.rerank fatal`, {\n error,\n source: `${this.name}.rerank`,\n })\n throw error\n }\n\n const json: unknown = await response.json()\n if (!isCohereRerankResponse(json)) {\n throw new Error('Cohere rerank response had an unexpected shape')\n }\n\n const searchUnits = json.meta?.billed_units?.search_units\n const usage: TokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n ...(searchUnits !== undefined\n ? {\n billed: { quantity: searchUnits, unit: 'units' },\n unitsBilled: searchUnits,\n }\n : {}),\n }\n\n return {\n id: json.id ?? this.generateId(),\n ranking: json.results.map((r) => ({\n index: r.index,\n score: r.relevance_score,\n })),\n usage,\n }\n }\n}\n\n/**\n * Creates a Cohere rerank adapter with an explicit API key. Type resolution\n * (per-model provider options) happens here at the call site.\n *\n * @example\n * ```typescript\n * const adapter = createCohereRerank('rerank-v3.5', 'co-...')\n * ```\n */\nexport function createCohereRerank<TModel extends CohereRerankModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<CohereClientConfig, 'apiKey'>,\n): CohereRerankAdapter<TModel> {\n return new CohereRerankAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Cohere rerank adapter, reading `COHERE_API_KEY` from the\n * environment.\n *\n * @throws Error if `COHERE_API_KEY` is not found.\n *\n * @example\n * ```typescript\n * import { rerank } from '@tanstack/ai'\n * import { cohereRerank } from '@tanstack/ai-cohere'\n *\n * const { rerankedDocuments } = await rerank({\n * adapter: cohereRerank('rerank-v3.5'),\n * query: 'talk about rain',\n * documents: ['sunny day', 'rainy afternoon'],\n * })\n * ```\n */\nexport function cohereRerank<TModel extends CohereRerankModel>(\n model: TModel,\n config?: Omit<CohereClientConfig, 'apiKey'>,\n): CohereRerankAdapter<TModel> {\n return createCohereRerank(model, getCohereApiKeyFromEnv(), config)\n}\n"],"mappings":";;;AAuBA,SAAS,uBAAuB,OAA+C;CAC7E,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,UAAW,MAAgC;CACjD,OACE,MAAM,QAAQ,OAAO,KACrB,QAAQ,OACL,MACC,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAA0B,UAAU,YAC5C,OAAQ,EAAoC,oBAC1C,QACN;AAEJ;;;;;;;;AASA,IAAa,sBAAb,cAEU,kBAAoE;CAC5E,OAAgB;CAEhB;CACA;CACA;CAEA,YAAY,QAA4B,OAAe;EACrD,MAAM,CAAC,GAAG,KAAK;EACf,KAAK,SAAS,OAAO;EACrB,KAAK,WAAW,OAAO,WAAA,yBAAA,CAAoC,QACzD,QACA,EACF;EACA,KAAK,UAAU,OAAO,WAAW,CAAC;CACpC;CAEA,MAAM,OACJ,SAC8B;EAC9B,MAAM,EAAE,OAAO,OAAO,WAAW,MAAM,cAAc,aAAa,WAChE;EAEF,MAAM,OAAgC;GAAE;GAAO;GAAO;EAAU;EAChE,IAAI,SAAS,KAAA,GAAW,KAAK,WAAW;EACxC,IAAI,cAAc,oBAAoB,KAAA,GACpC,KAAK,wBAAwB,aAAa;EAG5C,OAAO,QACL,4BAA4B,KAAK,KAAK,SAAS,MAAM,aAAa,UAAU,UAC5E;GAAE,UAAU,KAAK;GAAM;EAAM,CAC/B;EAEA,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,MAAM,GAAG,KAAK,QAAQ,aAAa;IAClD,QAAQ;IACR,SAAS;KACP,eAAe,UAAU,KAAK;KAC9B,gBAAgB;KAChB,GAAG,KAAK;IACV;IACA,MAAM,KAAK,UAAU,IAAI;IACzB,GAAI,cAAc,EAAE,QAAQ,YAAY,IAAI,CAAC;GAC/C,CAAC;EACH,SAAS,OAAO;GACd,OAAO,OAAO,GAAG,KAAK,KAAK,gBAAgB;IACzC;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;EAEA,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GACnD,MAAM,wBAAQ,IAAI,MAChB,iCAAiC,SAAS,OAAO,GAAG,SAAS,aAC3D,SAAS,MAAM,WAAW,IAE9B;GACA,OAAO,OAAO,GAAG,KAAK,KAAK,gBAAgB;IACzC;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;EAEA,MAAM,OAAgB,MAAM,SAAS,KAAK;EAC1C,IAAI,CAAC,uBAAuB,IAAI,GAC9B,MAAM,IAAI,MAAM,gDAAgD;EAGlE,MAAM,cAAc,KAAK,MAAM,cAAc;EAC7C,MAAM,QAAoB;GACxB,cAAc;GACd,kBAAkB;GAClB,aAAa;GACb,GAAI,gBAAgB,KAAA,IAChB;IACE,QAAQ;KAAE,UAAU;KAAa,MAAM;IAAQ;IAC/C,aAAa;GACf,IACA,CAAC;EACP;EAEA,OAAO;GACL,IAAI,KAAK,MAAM,KAAK,WAAW;GAC/B,SAAS,KAAK,QAAQ,KAAK,OAAO;IAChC,OAAO,EAAE;IACT,OAAO,EAAE;GACX,EAAE;GACF;EACF;CACF;AACF;;;;;;;;;;AAWA,SAAgB,mBACd,OACA,QACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AAC7D;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aACd,OACA,QAC6B;CAC7B,OAAO,mBAAmB,OAAO,uBAAuB,GAAG,MAAM;AACnE"}
1
+ {"version":3,"file":"rerank.js","names":[],"sources":["../../../src/adapters/rerank.ts"],"sourcesContent":["import { BaseRerankAdapter } from '@tanstack/ai/adapters'\nimport { resolveCohereTransport, getCohereApiKeyFromEnv } from '../utils/client'\nimport type { CohereClientConfig } from '../utils/client'\nimport type {\n CohereRerankModel,\n InferCohereRerankProviderOptions,\n} from '../model-meta'\nimport type {\n RerankAdapterResult,\n RerankOptions,\n TokenUsage,\n} from '@tanstack/ai'\n\n/** Shape of the Cohere `/v2/rerank` response we depend on. */\ninterface CohereRerankResponse {\n id?: string\n results: Array<{ index: number; relevance_score: number }>\n meta?: { billed_units?: { search_units?: number } }\n}\n\nfunction isCohereRerankResponse(value: unknown): value is CohereRerankResponse {\n if (typeof value !== 'object' || value === null) return false\n const results = (value as { results?: unknown }).results\n return (\n Array.isArray(results) &&\n results.every(\n (r) =>\n typeof r === 'object' &&\n r !== null &&\n typeof (r as { index?: unknown }).index === 'number' &&\n typeof (r as { relevance_score?: unknown }).relevance_score ===\n 'number',\n )\n )\n}\n\n/**\n * Cohere rerank adapter.\n *\n * Talks to Cohere's `/v2/rerank` endpoint over raw `fetch` — no SDK. Returns\n * scored indices into the submitted documents; the `rerank()` activity maps\n * those back to the caller's original documents.\n */\nexport class CohereRerankAdapter<\n TModel extends CohereRerankModel,\n> extends BaseRerankAdapter<TModel, InferCohereRerankProviderOptions<TModel>> {\n readonly name = 'cohere' as const\n\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly headers: Record<string, string>\n\n constructor(config: CohereClientConfig, model: TModel) {\n super({}, model)\n this.apiKey = config.apiKey\n const transport = resolveCohereTransport(config)\n this.baseUrl = transport.baseUrl\n this.headers = transport.headers\n }\n\n async rerank(\n options: RerankOptions<InferCohereRerankProviderOptions<TModel>>,\n ): Promise<RerankAdapterResult> {\n const { model, query, documents, topN, modelOptions, abortSignal, logger } =\n options\n\n const body: Record<string, unknown> = { model, query, documents }\n if (topN !== undefined) body['top_n'] = topN\n if (modelOptions?.maxTokensPerDoc !== undefined) {\n body['max_tokens_per_doc'] = modelOptions.maxTokensPerDoc\n }\n\n logger.request(\n `activity=rerank provider=${this.name} model=${model} documents=${documents.length}`,\n { provider: this.name, model },\n )\n\n let response: Response\n try {\n response = await fetch(`${this.baseUrl}/v2/rerank`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n ...this.headers,\n },\n body: JSON.stringify(body),\n ...(abortSignal ? { signal: abortSignal } : {}),\n })\n } catch (error) {\n logger.errors(`${this.name}.rerank fatal`, {\n error,\n source: `${this.name}.rerank`,\n })\n throw error\n }\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n const error = new Error(\n `Cohere rerank request failed: ${response.status} ${response.statusText}${\n detail ? ` — ${detail}` : ''\n }`,\n )\n logger.errors(`${this.name}.rerank fatal`, {\n error,\n source: `${this.name}.rerank`,\n })\n throw error\n }\n\n const json: unknown = await response.json()\n if (!isCohereRerankResponse(json)) {\n throw new Error('Cohere rerank response had an unexpected shape')\n }\n\n const searchUnits = json.meta?.billed_units?.search_units\n const usage: TokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n ...(searchUnits !== undefined\n ? {\n billed: { quantity: searchUnits, unit: 'units' },\n unitsBilled: searchUnits,\n }\n : {}),\n }\n\n return {\n id: json.id ?? this.generateId(),\n ranking: json.results.map((r) => ({\n index: r.index,\n score: r.relevance_score,\n })),\n usage,\n }\n }\n}\n\n/**\n * Creates a Cohere rerank adapter with an explicit API key. Type resolution\n * (per-model provider options) happens here at the call site.\n *\n * @example\n * ```typescript\n * const adapter = createCohereRerank('rerank-v3.5', 'co-...')\n * ```\n */\nexport function createCohereRerank<TModel extends CohereRerankModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<CohereClientConfig, 'apiKey'>,\n): CohereRerankAdapter<TModel> {\n return new CohereRerankAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Cohere rerank adapter, reading `COHERE_API_KEY` from the\n * environment.\n *\n * @throws Error if `COHERE_API_KEY` is not found.\n *\n * @example\n * ```typescript\n * import { rerank } from '@tanstack/ai'\n * import { cohereRerank } from '@tanstack/ai-cohere'\n *\n * const { rerankedDocuments } = await rerank({\n * adapter: cohereRerank('rerank-v3.5'),\n * query: 'talk about rain',\n * documents: ['sunny day', 'rainy afternoon'],\n * })\n * ```\n */\nexport function cohereRerank<TModel extends CohereRerankModel>(\n model: TModel,\n config?: Omit<CohereClientConfig, 'apiKey'>,\n): CohereRerankAdapter<TModel> {\n return createCohereRerank(model, getCohereApiKeyFromEnv(), config)\n}\n"],"mappings":";;;AAoBA,SAAS,uBAAuB,OAA+C;CAC7E,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,UAAW,MAAgC;CACjD,OACE,MAAM,QAAQ,OAAO,KACrB,QAAQ,OACL,MACC,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAA0B,UAAU,YAC5C,OAAQ,EAAoC,oBAC1C,QACN;AAEJ;;;;;;;;AASA,IAAa,sBAAb,cAEU,kBAAoE;CAC5E,OAAgB;CAEhB;CACA;CACA;CAEA,YAAY,QAA4B,OAAe;EACrD,MAAM,CAAC,GAAG,KAAK;EACf,KAAK,SAAS,OAAO;EACrB,MAAM,YAAY,uBAAuB,MAAM;EAC/C,KAAK,UAAU,UAAU;EACzB,KAAK,UAAU,UAAU;CAC3B;CAEA,MAAM,OACJ,SAC8B;EAC9B,MAAM,EAAE,OAAO,OAAO,WAAW,MAAM,cAAc,aAAa,WAChE;EAEF,MAAM,OAAgC;GAAE;GAAO;GAAO;EAAU;EAChE,IAAI,SAAS,KAAA,GAAW,KAAK,WAAW;EACxC,IAAI,cAAc,oBAAoB,KAAA,GACpC,KAAK,wBAAwB,aAAa;EAG5C,OAAO,QACL,4BAA4B,KAAK,KAAK,SAAS,MAAM,aAAa,UAAU,UAC5E;GAAE,UAAU,KAAK;GAAM;EAAM,CAC/B;EAEA,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,MAAM,GAAG,KAAK,QAAQ,aAAa;IAClD,QAAQ;IACR,SAAS;KACP,eAAe,UAAU,KAAK;KAC9B,gBAAgB;KAChB,GAAG,KAAK;IACV;IACA,MAAM,KAAK,UAAU,IAAI;IACzB,GAAI,cAAc,EAAE,QAAQ,YAAY,IAAI,CAAC;GAC/C,CAAC;EACH,SAAS,OAAO;GACd,OAAO,OAAO,GAAG,KAAK,KAAK,gBAAgB;IACzC;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;EAEA,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,SAAS,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GACnD,MAAM,wBAAQ,IAAI,MAChB,iCAAiC,SAAS,OAAO,GAAG,SAAS,aAC3D,SAAS,MAAM,WAAW,IAE9B;GACA,OAAO,OAAO,GAAG,KAAK,KAAK,gBAAgB;IACzC;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;EAEA,MAAM,OAAgB,MAAM,SAAS,KAAK;EAC1C,IAAI,CAAC,uBAAuB,IAAI,GAC9B,MAAM,IAAI,MAAM,gDAAgD;EAGlE,MAAM,cAAc,KAAK,MAAM,cAAc;EAC7C,MAAM,QAAoB;GACxB,cAAc;GACd,kBAAkB;GAClB,aAAa;GACb,GAAI,gBAAgB,KAAA,IAChB;IACE,QAAQ;KAAE,UAAU;KAAa,MAAM;IAAQ;IAC/C,aAAa;GACf,IACA,CAAC;EACP;EAEA,OAAO;GACL,IAAI,KAAK,MAAM,KAAK,WAAW;GAC/B,SAAS,KAAK,QAAQ,KAAK,OAAO;IAChC,OAAO,EAAE;IACT,OAAO,EAAE;GACX,EAAE;GACF;EACF;CACF;AACF;;;;;;;;;;AAWA,SAAgB,mBACd,OACA,QACA,QAC6B;CAC7B,OAAO,IAAI,oBAAoB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AAC7D;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aACd,OACA,QAC6B;CAC7B,OAAO,mBAAmB,OAAO,uBAAuB,GAAG,MAAM;AACnE"}
@@ -5,9 +5,20 @@
5
5
  export interface CohereClientConfig {
6
6
  /** Cohere API key. */
7
7
  apiKey: string;
8
- /** Optional base URL override (defaults to `https://api.cohere.com`). */
8
+ /**
9
+ * Base URL for every request (defaults to `https://api.cohere.com`). Same
10
+ * option name as the other adapters, so a gateway config can be spread into
11
+ * any of them. Wins over `baseUrl` when both are set.
12
+ */
13
+ baseURL?: string;
14
+ /** Alias of `baseURL`. */
9
15
  baseUrl?: string;
10
- /** Optional default headers to include with every request. */
16
+ /**
17
+ * Headers sent with every request. Same option name as the other adapters.
18
+ * Wins over `headers` when both are set.
19
+ */
20
+ defaultHeaders?: Record<string, string>;
21
+ /** Alias of `defaultHeaders`. */
11
22
  headers?: Record<string, string>;
12
23
  /**
13
24
  * Cohere's embed API does not fetch remote image URLs itself. When this is
@@ -19,6 +30,11 @@ export interface CohereClientConfig {
19
30
  timeout?: number;
20
31
  }
21
32
  export declare const COHERE_DEFAULT_BASE_URL = "https://api.cohere.com";
33
+ /** Resolve the effective base URL (no trailing slash) and headers. */
34
+ export declare function resolveCohereTransport(config: CohereClientConfig): {
35
+ baseUrl: string;
36
+ headers: Record<string, string>;
37
+ };
22
38
  /**
23
39
  * Gets Cohere API key from environment variables.
24
40
  *
@@ -1,6 +1,13 @@
1
1
  import { getApiKeyFromEnv } from "@tanstack/ai-utils";
2
2
  //#region src/utils/client.ts
3
3
  var COHERE_DEFAULT_BASE_URL = "https://api.cohere.com";
4
+ /** Resolve the effective base URL (no trailing slash) and headers. */
5
+ function resolveCohereTransport(config) {
6
+ return {
7
+ baseUrl: (config.baseURL ?? config.baseUrl ?? "https://api.cohere.com").replace(/\/+$/, ""),
8
+ headers: config.defaultHeaders ?? config.headers ?? {}
9
+ };
10
+ }
4
11
  /**
5
12
  * Gets Cohere API key from environment variables.
6
13
  *
@@ -14,6 +21,6 @@ function getCohereApiKeyFromEnv() {
14
21
  return getApiKeyFromEnv("COHERE_API_KEY");
15
22
  }
16
23
  //#endregion
17
- export { COHERE_DEFAULT_BASE_URL, getCohereApiKeyFromEnv };
24
+ export { COHERE_DEFAULT_BASE_URL, getCohereApiKeyFromEnv, resolveCohereTransport };
18
25
 
19
26
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\n\n/**\n * Configuration for the Cohere HTTP client used by the adapters in this\n * package. Requests are made with plain `fetch` — no Cohere SDK dependency.\n */\nexport interface CohereClientConfig {\n /** Cohere API key. */\n apiKey: string\n\n /** Optional base URL override (defaults to `https://api.cohere.com`). */\n baseUrl?: string\n\n /** Optional default headers to include with every request. */\n headers?: Record<string, string>\n\n /**\n * Cohere's embed API does not fetch remote image URLs itself. When this is\n * enabled the adapter downloads http(s) image URLs and inlines them as\n * base64 `data:` URIs before sending the request. Disabled by default.\n */\n allowUrlFetch?: boolean\n\n /** Request timeout in milliseconds for API and image URL fetches (default: 30_000). */\n timeout?: number\n}\n\nexport const COHERE_DEFAULT_BASE_URL = 'https://api.cohere.com'\n\n/**\n * Gets Cohere API key from environment variables.\n *\n * Looks for `COHERE_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @throws Error if COHERE_API_KEY is not found\n */\nexport function getCohereApiKeyFromEnv(): string {\n return getApiKeyFromEnv('COHERE_API_KEY')\n}\n"],"mappings":";;AA2BA,IAAa,0BAA0B;;;;;;;;;;AAWvC,SAAgB,yBAAiC;CAC/C,OAAO,iBAAiB,gBAAgB;AAC1C"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\n\n/**\n * Configuration for the Cohere HTTP client used by the adapters in this\n * package. Requests are made with plain `fetch` — no Cohere SDK dependency.\n */\nexport interface CohereClientConfig {\n /** Cohere API key. */\n apiKey: string\n\n /**\n * Base URL for every request (defaults to `https://api.cohere.com`). Same\n * option name as the other adapters, so a gateway config can be spread into\n * any of them. Wins over `baseUrl` when both are set.\n */\n baseURL?: string\n\n /** Alias of `baseURL`. */\n baseUrl?: string\n\n /**\n * Headers sent with every request. Same option name as the other adapters.\n * Wins over `headers` when both are set.\n */\n defaultHeaders?: Record<string, string>\n\n /** Alias of `defaultHeaders`. */\n headers?: Record<string, string>\n\n /**\n * Cohere's embed API does not fetch remote image URLs itself. When this is\n * enabled the adapter downloads http(s) image URLs and inlines them as\n * base64 `data:` URIs before sending the request. Disabled by default.\n */\n allowUrlFetch?: boolean\n\n /** Request timeout in milliseconds for API and image URL fetches (default: 30_000). */\n timeout?: number\n}\n\nexport const COHERE_DEFAULT_BASE_URL = 'https://api.cohere.com'\n\n/** Resolve the effective base URL (no trailing slash) and headers. */\nexport function resolveCohereTransport(config: CohereClientConfig): {\n baseUrl: string\n headers: Record<string, string>\n} {\n return {\n baseUrl: (\n config.baseURL ??\n config.baseUrl ??\n COHERE_DEFAULT_BASE_URL\n ).replace(/\\/+$/, ''),\n headers: config.defaultHeaders ?? config.headers ?? {},\n }\n}\n\n/**\n * Gets Cohere API key from environment variables.\n *\n * Looks for `COHERE_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @throws Error if COHERE_API_KEY is not found\n */\nexport function getCohereApiKeyFromEnv(): string {\n return getApiKeyFromEnv('COHERE_API_KEY')\n}\n"],"mappings":";;AAwCA,IAAa,0BAA0B;;AAGvC,SAAgB,uBAAuB,QAGrC;CACA,OAAO;EACL,UACE,OAAO,WACP,OAAO,WAAA,yBAAA,CAEP,QAAQ,QAAQ,EAAE;EACpB,SAAS,OAAO,kBAAkB,OAAO,WAAW,CAAC;CACvD;AACF;;;;;;;;;;AAWA,SAAgB,yBAAiC;CAC/C,OAAO,iBAAiB,gBAAgB;AAC1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-cohere",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "description": "Cohere adapter for TanStack AI — multimodal embeddings and document reranking.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -53,12 +53,12 @@
53
53
  "retrieval"
54
54
  ],
55
55
  "peerDependencies": {
56
- "@tanstack/ai": "^0.52.0"
56
+ "@tanstack/ai": "^0.53.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@vitest/coverage-v8": "4.1.10",
60
60
  "vite": "^8.2.1",
61
- "@tanstack/ai": "0.52.0"
61
+ "@tanstack/ai": "0.53.0"
62
62
  },
63
63
  "dependencies": {
64
64
  "@tanstack/ai-utils": "^0.4.0"
@@ -2,7 +2,7 @@ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'
2
2
  import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
3
3
  import { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'
4
4
  import { resolveEmbeddingInput } from '@tanstack/ai'
5
- import { getCohereApiKeyFromEnv } from '../utils/client'
5
+ import { getCohereApiKeyFromEnv, resolveCohereTransport } from '../utils/client'
6
6
  import type {
7
7
  EmbeddingOptions,
8
8
  EmbeddingResult,
@@ -22,7 +22,6 @@ import type { CohereClientConfig } from '../utils/client'
22
22
  */
23
23
  export interface CohereEmbeddingConfig extends CohereClientConfig {}
24
24
 
25
- const DEFAULT_BASE_URL = 'https://api.cohere.com'
26
25
  const DEFAULT_TIMEOUT_MS = 30_000
27
26
 
28
27
  /**
@@ -184,14 +183,15 @@ export class CohereEmbeddingAdapter<
184
183
  )
185
184
 
186
185
  const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS
186
+ const transport = resolveCohereTransport(this.clientConfig)
187
187
  const response = await fetchWithTimeout(
188
- `${this.clientConfig.baseUrl ?? DEFAULT_BASE_URL}/v2/embed`,
188
+ `${transport.baseUrl}/v2/embed`,
189
189
  {
190
190
  method: 'POST',
191
191
  headers: {
192
192
  Authorization: `Bearer ${this.clientConfig.apiKey}`,
193
193
  'Content-Type': 'application/json',
194
- ...this.clientConfig.headers,
194
+ ...transport.headers,
195
195
  },
196
196
  body: JSON.stringify(body),
197
197
  },
@@ -1,8 +1,5 @@
1
1
  import { BaseRerankAdapter } from '@tanstack/ai/adapters'
2
- import {
3
- COHERE_DEFAULT_BASE_URL,
4
- getCohereApiKeyFromEnv,
5
- } from '../utils/client'
2
+ import { resolveCohereTransport, getCohereApiKeyFromEnv } from '../utils/client'
6
3
  import type { CohereClientConfig } from '../utils/client'
7
4
  import type {
8
5
  CohereRerankModel,
@@ -56,11 +53,9 @@ export class CohereRerankAdapter<
56
53
  constructor(config: CohereClientConfig, model: TModel) {
57
54
  super({}, model)
58
55
  this.apiKey = config.apiKey
59
- this.baseUrl = (config.baseUrl ?? COHERE_DEFAULT_BASE_URL).replace(
60
- /\/+$/,
61
- '',
62
- )
63
- this.headers = config.headers ?? {}
56
+ const transport = resolveCohereTransport(config)
57
+ this.baseUrl = transport.baseUrl
58
+ this.headers = transport.headers
64
59
  }
65
60
 
66
61
  async rerank(
@@ -8,10 +8,23 @@ export interface CohereClientConfig {
8
8
  /** Cohere API key. */
9
9
  apiKey: string
10
10
 
11
- /** Optional base URL override (defaults to `https://api.cohere.com`). */
11
+ /**
12
+ * Base URL for every request (defaults to `https://api.cohere.com`). Same
13
+ * option name as the other adapters, so a gateway config can be spread into
14
+ * any of them. Wins over `baseUrl` when both are set.
15
+ */
16
+ baseURL?: string
17
+
18
+ /** Alias of `baseURL`. */
12
19
  baseUrl?: string
13
20
 
14
- /** Optional default headers to include with every request. */
21
+ /**
22
+ * Headers sent with every request. Same option name as the other adapters.
23
+ * Wins over `headers` when both are set.
24
+ */
25
+ defaultHeaders?: Record<string, string>
26
+
27
+ /** Alias of `defaultHeaders`. */
15
28
  headers?: Record<string, string>
16
29
 
17
30
  /**
@@ -27,6 +40,21 @@ export interface CohereClientConfig {
27
40
 
28
41
  export const COHERE_DEFAULT_BASE_URL = 'https://api.cohere.com'
29
42
 
43
+ /** Resolve the effective base URL (no trailing slash) and headers. */
44
+ export function resolveCohereTransport(config: CohereClientConfig): {
45
+ baseUrl: string
46
+ headers: Record<string, string>
47
+ } {
48
+ return {
49
+ baseUrl: (
50
+ config.baseURL ??
51
+ config.baseUrl ??
52
+ COHERE_DEFAULT_BASE_URL
53
+ ).replace(/\/+$/, ''),
54
+ headers: config.defaultHeaders ?? config.headers ?? {},
55
+ }
56
+ }
57
+
30
58
  /**
31
59
  * Gets Cohere API key from environment variables.
32
60
  *