@tanstack/ai-cohere 0.0.0 → 0.1.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
@@ -15,12 +15,11 @@
15
15
 
16
16
  # @tanstack/ai-cohere
17
17
 
18
- Cohere adapter for [TanStack AI](https://tanstack.com/ai). Reorder candidate
19
- documents by relevance to a query with Cohere's rerank models — the precision
20
- step for RAG and search pipelines.
18
+ Cohere adapter for [TanStack AI](https://tanstack.com/ai). It gives you two
19
+ things: multimodal embeddings with `embed-v4.0`, and document reranking with
20
+ Cohere's rerank models — the precision step for RAG and search pipelines.
21
21
 
22
- This adapter is **rerank-only**. For chat, summarization, embeddings, or media,
23
- use OpenAI, Anthropic, or Gemini.
22
+ For chat, summarization, or media, use OpenAI, Anthropic, or Gemini.
24
23
 
25
24
  ## Install
26
25
 
@@ -28,8 +27,63 @@ use OpenAI, Anthropic, or Gemini.
28
27
  pnpm add @tanstack/ai @tanstack/ai-cohere
29
28
  ```
30
29
 
30
+ ## Setup
31
+
32
+ Get your API key from the [Cohere Dashboard](https://dashboard.cohere.com/api-keys) and set it as an environment variable:
33
+
34
+ ```bash
35
+ export COHERE_API_KEY="..."
36
+ ```
37
+
31
38
  ## Usage
32
39
 
40
+ ### Embedding Adapter
41
+
42
+ ```typescript
43
+ import { cohereEmbedding } from '@tanstack/ai-cohere'
44
+ import { embed } from '@tanstack/ai'
45
+
46
+ const adapter = cohereEmbedding('embed-v4.0')
47
+
48
+ const result = await embed({
49
+ adapter,
50
+ input: ['a red guitar', 'a blue drum kit'],
51
+ modelOptions: { inputType: 'search_document' },
52
+ })
53
+
54
+ console.log(result.embeddings[0].vector)
55
+ ```
56
+
57
+ ### Multimodal Inputs
58
+
59
+ embed-v4.0 embeds text, images, and fused text+image items (one vector per input item). Fuse parts by nesting them in an array — the outer array is the item list:
60
+
61
+ ```typescript
62
+ const result = await embed({
63
+ adapter,
64
+ input: [
65
+ 'a red guitar',
66
+ {
67
+ type: 'image',
68
+ source: { type: 'data', value: base64Png, mimeType: 'image/png' },
69
+ },
70
+ // A nested array fuses its parts into a single vector.
71
+ [
72
+ { type: 'text', content: 'product photo' },
73
+ {
74
+ type: 'image',
75
+ source: { type: 'data', value: base64Jpeg, mimeType: 'image/jpeg' },
76
+ },
77
+ ],
78
+ ],
79
+ modelOptions: { inputType: 'search_document' },
80
+ })
81
+ ```
82
+
83
+ Cohere does not fetch remote image URLs. Pass base64 data or a `data:` URI, or enable `allowUrlFetch` in the adapter config to have the adapter download http(s) URLs and inline them.
84
+
85
+ ### Rerank Adapter
86
+
33
87
  ```typescript
34
88
  import { rerank } from '@tanstack/ai'
35
89
  import { cohereRerank } from '@tanstack/ai-cohere'
@@ -44,12 +98,57 @@ const { ranking, rerankedDocuments } = await rerank({
44
98
  console.log(rerankedDocuments[0]) // 'rainy afternoon in the city'
45
99
  ```
46
100
 
47
- The adapter reads `COHERE_API_KEY` from the environment. To pass a key
48
- explicitly, use `createCohereRerank('rerank-v3.5', 'co-...')`.
101
+ ### With Explicit API Key
102
+
103
+ Both adapters read `COHERE_API_KEY` from the environment. To pass a key
104
+ explicitly, use the `create*` factories:
105
+
106
+ ```typescript
107
+ import { createCohereEmbedding, createCohereRerank } from '@tanstack/ai-cohere'
108
+
109
+ const embedAdapter = createCohereEmbedding(
110
+ 'embed-v4.0',
111
+ process.env.COHERE_API_KEY!,
112
+ )
113
+ const rerankAdapter = createCohereRerank('rerank-v3.5', 'co-...')
114
+ ```
115
+
116
+ ## Supported Models
117
+
118
+ ### Embedding Models
119
+
120
+ - `embed-v4.0` - Multimodal embedding model (text + images, Matryoshka dimensions via the top-level `dimensions` option)
121
+
122
+ ### Rerank Models
49
123
 
50
- ## <a href="https://tanstack.com/ai/latest/docs/rerank/rerank">Read the docs -></a>
124
+ - `rerank-v3.5`
125
+ - `rerank-english-v3.0`
126
+ - `rerank-multilingual-v3.0`
127
+
128
+ ## Features
129
+
130
+ - ✅ Embeddings (batch, one request per input array)
131
+ - ✅ Multimodal embedding input (text + images + fused text/image items)
132
+ - ✅ Dimension reduction (`dimensions` → Cohere `output_dimension`)
133
+ - ✅ Document reranking
134
+ - ❌ Chat / text generation
135
+ - ❌ Image generation
136
+
137
+ ## Tree-Shakeable Adapters
138
+
139
+ This package uses tree-shakeable adapters, so you only import what you need:
140
+
141
+ ```typescript
142
+ import { cohereEmbedding, cohereRerank } from '@tanstack/ai-cohere'
143
+ ```
144
+
145
+ ## <a href="https://tanstack.com/ai/latest/docs/adapters/cohere">Read the docs -></a>
51
146
 
52
147
  - [Reranking Guide](https://tanstack.com/ai/latest/docs/rerank/rerank) — object
53
148
  documents, RAG pipelines, options, and the result shape.
54
149
  - [Cohere Adapter](https://tanstack.com/ai/latest/docs/adapters/cohere) —
55
150
  models, configuration, and explicit API keys.
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,86 @@
1
+ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters';
2
+ import { EmbeddingOptions, EmbeddingResult, ImagePart } from '@tanstack/ai';
3
+ import { CohereEmbeddingModel, CohereEmbeddingModelInputModalitiesByName, CohereEmbeddingModelProviderOptionsByName } from '../model-meta.js';
4
+ import { CohereEmbeddingProviderOptions } from '../embedding/embedding-provider-options.js';
5
+ import { CohereClientConfig } from '../utils/client.js';
6
+ /**
7
+ * Configuration for Cohere embedding adapter.
8
+ */
9
+ export interface CohereEmbeddingConfig extends CohereClientConfig {
10
+ }
11
+ /**
12
+ * Cohere Embedding Adapter
13
+ *
14
+ * Tree-shakeable adapter for Cohere multimodal embeddings (embed-v4.0),
15
+ * implemented with plain `fetch` against the v2/embed endpoint — no Cohere
16
+ * SDK dependency.
17
+ *
18
+ * Features:
19
+ * - Batch embedding (one request for the whole input array)
20
+ * - Multimodal inputs: text, images, and fused text+image items (one vector
21
+ * per input item)
22
+ * - Matryoshka dimension reduction via the top-level `dimensions` option
23
+ * (mapped to Cohere's `output_dimension`)
24
+ */
25
+ export declare class CohereEmbeddingAdapter<TModel extends CohereEmbeddingModel> extends BaseEmbeddingAdapter<TModel, CohereEmbeddingProviderOptions, CohereEmbeddingModelProviderOptionsByName, CohereEmbeddingModelInputModalitiesByName> {
26
+ readonly name: "cohere";
27
+ protected clientConfig: CohereEmbeddingConfig;
28
+ constructor(config: CohereEmbeddingConfig, model: TModel);
29
+ createEmbeddings(options: EmbeddingOptions<CohereEmbeddingProviderOptions>): Promise<EmbeddingResult>;
30
+ /**
31
+ * Resolves an image part to a URL Cohere accepts. Cohere does not fetch
32
+ * remote image URLs, so everything is normalized to a `data:` URI unless
33
+ * the caller already provided one.
34
+ */
35
+ protected resolveImageUrl(image: ImagePart): Promise<string>;
36
+ }
37
+ /**
38
+ * Creates a Cohere embedding adapter with explicit API key.
39
+ * Type resolution happens here at the call site.
40
+ *
41
+ * @param model - The model name (e.g., 'embed-v4.0')
42
+ * @param apiKey - Your Cohere API key
43
+ * @param config - Optional additional configuration
44
+ * @returns Configured Cohere embedding adapter instance with resolved types
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const adapter = createCohereEmbedding('embed-v4.0', 'api_key');
49
+ *
50
+ * const result = await embed({
51
+ * adapter,
52
+ * input: 'a red guitar',
53
+ * modelOptions: { inputType: 'search_document' }
54
+ * });
55
+ * ```
56
+ */
57
+ export declare function createCohereEmbedding<TModel extends CohereEmbeddingModel>(model: TModel, apiKey: string, config?: Omit<CohereEmbeddingConfig, 'apiKey'>): CohereEmbeddingAdapter<TModel>;
58
+ /**
59
+ * Creates a Cohere embedding adapter using the `COHERE_API_KEY` environment variable.
60
+ * Type resolution happens here at the call site.
61
+ *
62
+ * Looks for `COHERE_API_KEY` in:
63
+ * - `process.env` (Node.js)
64
+ * - `window.env` (Browser with injected env)
65
+ *
66
+ * @param model - The model name (e.g., 'embed-v4.0')
67
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
68
+ * @returns Configured Cohere embedding adapter instance with resolved types
69
+ * @throws Error if COHERE_API_KEY is not found in environment
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Automatically uses COHERE_API_KEY from environment
74
+ * const adapter = cohereEmbedding('embed-v4.0');
75
+ *
76
+ * const result = await embed({
77
+ * adapter,
78
+ * input: ['a red guitar', 'a blue drum kit'],
79
+ * modelOptions: { inputType: 'search_query' },
80
+ * dimensions: 1024
81
+ * });
82
+ *
83
+ * console.log(result.embeddings[0].vector)
84
+ * ```
85
+ */
86
+ export declare function cohereEmbedding<TModel extends CohereEmbeddingModel>(model: TModel, config?: Omit<CohereEmbeddingConfig, 'apiKey'>): CohereEmbeddingAdapter<TModel>;
@@ -0,0 +1,210 @@
1
+ import { getCohereApiKeyFromEnv } from "../utils/client.js";
2
+ import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters";
3
+ import { toRunErrorPayload } from "@tanstack/ai/adapter-internals";
4
+ import { arrayBufferToBase64, generateId } from "@tanstack/ai-utils";
5
+ import { resolveEmbeddingInput } from "@tanstack/ai";
6
+ //#region src/adapters/embedding.ts
7
+ var DEFAULT_BASE_URL = "https://api.cohere.com";
8
+ var DEFAULT_TIMEOUT_MS = 3e4;
9
+ /**
10
+ * Returns true when `url` is malformed, non-http(s), or targets a private /
11
+ * loopback / link-local host. Used to block SSRF via `allowUrlFetch`.
12
+ */
13
+ function isPrivateOrInternalUrl(url) {
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(url);
17
+ } catch {
18
+ return true;
19
+ }
20
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return true;
21
+ const host = parsed.hostname.toLowerCase();
22
+ if (host === "localhost" || host.endsWith(".localhost") || host === "::1" || host === "[::1]" || host.startsWith("127.") || host.startsWith("10.") || host.startsWith("192.168.") || host.startsWith("169.254.") || /^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
23
+ return false;
24
+ }
25
+ async function fetchWithTimeout(url, init, timeoutMs) {
26
+ const controller = new AbortController();
27
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
28
+ try {
29
+ return await fetch(url, {
30
+ ...init,
31
+ signal: controller.signal
32
+ });
33
+ } finally {
34
+ clearTimeout(timeoutId);
35
+ }
36
+ }
37
+ /**
38
+ * Cohere Embedding Adapter
39
+ *
40
+ * Tree-shakeable adapter for Cohere multimodal embeddings (embed-v4.0),
41
+ * implemented with plain `fetch` against the v2/embed endpoint — no Cohere
42
+ * SDK dependency.
43
+ *
44
+ * Features:
45
+ * - Batch embedding (one request for the whole input array)
46
+ * - Multimodal inputs: text, images, and fused text+image items (one vector
47
+ * per input item)
48
+ * - Matryoshka dimension reduction via the top-level `dimensions` option
49
+ * (mapped to Cohere's `output_dimension`)
50
+ */
51
+ var CohereEmbeddingAdapter = class extends BaseEmbeddingAdapter {
52
+ name = "cohere";
53
+ clientConfig;
54
+ constructor(config, model) {
55
+ super(model, {});
56
+ this.clientConfig = config;
57
+ }
58
+ async createEmbeddings(options) {
59
+ const { model, logger, modelOptions } = options;
60
+ try {
61
+ const inputType = modelOptions?.inputType;
62
+ if (!inputType) throw new Error(`Cohere embeddings require modelOptions.inputType ('search_document' | 'search_query' | 'classification' | 'clustering').`);
63
+ const resolved = resolveEmbeddingInput(options.input);
64
+ const inputs = await Promise.all(resolved.map(async (item) => {
65
+ const content = item.texts.map((text) => ({
66
+ type: "text",
67
+ text
68
+ }));
69
+ for (const image of item.images) content.push({
70
+ type: "image_url",
71
+ image_url: { url: await this.resolveImageUrl(image) }
72
+ });
73
+ return { content };
74
+ }));
75
+ const body = {
76
+ model,
77
+ inputs,
78
+ input_type: inputType,
79
+ embedding_types: ["float"]
80
+ };
81
+ const truncate = modelOptions?.truncate;
82
+ if (truncate !== void 0) body.truncate = truncate;
83
+ if (options.dimensions !== void 0) body.output_dimension = options.dimensions;
84
+ logger.request(`activity=embed provider=${this.name} model=${model} inputs=${inputs.length}`, {
85
+ provider: this.name,
86
+ model
87
+ });
88
+ const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS;
89
+ const response = await fetchWithTimeout(`${this.clientConfig.baseUrl ?? DEFAULT_BASE_URL}/v2/embed`, {
90
+ method: "POST",
91
+ headers: {
92
+ Authorization: `Bearer ${this.clientConfig.apiKey}`,
93
+ "Content-Type": "application/json",
94
+ ...this.clientConfig.headers
95
+ },
96
+ body: JSON.stringify(body)
97
+ }, timeoutMs);
98
+ if (!response.ok) {
99
+ const bodyText = await response.text();
100
+ let message = bodyText;
101
+ try {
102
+ const parsed = JSON.parse(bodyText);
103
+ if (typeof parsed === "object" && parsed !== null && "message" in parsed && typeof parsed.message === "string") message = parsed.message;
104
+ } catch {}
105
+ throw new Error(`Cohere embed failed (${response.status}): ${message}`);
106
+ }
107
+ const data = await response.json();
108
+ const vectors = data.embeddings?.float;
109
+ if (!vectors) throw new Error("Cohere embed response did not include float embeddings");
110
+ if (vectors.length !== inputs.length) throw new Error(`Cohere embed returned ${vectors.length} embeddings for ${inputs.length} inputs`);
111
+ const result = {
112
+ id: generateId(this.name),
113
+ model,
114
+ embeddings: vectors.map((vector, index) => ({
115
+ vector,
116
+ index
117
+ }))
118
+ };
119
+ const inputTokens = data.meta?.billed_units?.input_tokens;
120
+ if (inputTokens !== void 0) result.usage = {
121
+ promptTokens: inputTokens,
122
+ completionTokens: 0,
123
+ totalTokens: inputTokens
124
+ };
125
+ return result;
126
+ } catch (error) {
127
+ logger.errors(`${this.name}.createEmbeddings fatal`, {
128
+ error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),
129
+ source: `${this.name}.createEmbeddings`
130
+ });
131
+ throw error;
132
+ }
133
+ }
134
+ /**
135
+ * Resolves an image part to a URL Cohere accepts. Cohere does not fetch
136
+ * remote image URLs, so everything is normalized to a `data:` URI unless
137
+ * the caller already provided one.
138
+ */
139
+ async resolveImageUrl(image) {
140
+ const source = image.source;
141
+ if (source.type === "data") return `data:${source.mimeType};base64,${source.value}`;
142
+ if (source.value.startsWith("data:")) return source.value;
143
+ if (!this.clientConfig.allowUrlFetch) throw new Error("Cohere does not fetch remote image URLs; pass base64 data or a data: URI (or enable config.allowUrlFetch to have the adapter download it)");
144
+ if (isPrivateOrInternalUrl(source.value)) throw new Error(`Refusing to fetch internal or private URL for Cohere embedding: ${source.value}`);
145
+ const response = await fetchWithTimeout(source.value, void 0, this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS);
146
+ if (!response.ok) throw new Error(`Failed to fetch image URL for Cohere embedding (${response.status}): ${source.value}`);
147
+ return `data:${response.headers.get("content-type") ?? source.mimeType ?? "application/octet-stream"};base64,${arrayBufferToBase64(await response.arrayBuffer())}`;
148
+ }
149
+ };
150
+ /**
151
+ * Creates a Cohere embedding adapter with explicit API key.
152
+ * Type resolution happens here at the call site.
153
+ *
154
+ * @param model - The model name (e.g., 'embed-v4.0')
155
+ * @param apiKey - Your Cohere API key
156
+ * @param config - Optional additional configuration
157
+ * @returns Configured Cohere embedding adapter instance with resolved types
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const adapter = createCohereEmbedding('embed-v4.0', 'api_key');
162
+ *
163
+ * const result = await embed({
164
+ * adapter,
165
+ * input: 'a red guitar',
166
+ * modelOptions: { inputType: 'search_document' }
167
+ * });
168
+ * ```
169
+ */
170
+ function createCohereEmbedding(model, apiKey, config) {
171
+ return new CohereEmbeddingAdapter({
172
+ apiKey,
173
+ ...config
174
+ }, model);
175
+ }
176
+ /**
177
+ * Creates a Cohere embedding adapter using the `COHERE_API_KEY` environment variable.
178
+ * Type resolution happens here at the call site.
179
+ *
180
+ * Looks for `COHERE_API_KEY` in:
181
+ * - `process.env` (Node.js)
182
+ * - `window.env` (Browser with injected env)
183
+ *
184
+ * @param model - The model name (e.g., 'embed-v4.0')
185
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
186
+ * @returns Configured Cohere embedding adapter instance with resolved types
187
+ * @throws Error if COHERE_API_KEY is not found in environment
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // Automatically uses COHERE_API_KEY from environment
192
+ * const adapter = cohereEmbedding('embed-v4.0');
193
+ *
194
+ * const result = await embed({
195
+ * adapter,
196
+ * input: ['a red guitar', 'a blue drum kit'],
197
+ * modelOptions: { inputType: 'search_query' },
198
+ * dimensions: 1024
199
+ * });
200
+ *
201
+ * console.log(result.embeddings[0].vector)
202
+ * ```
203
+ */
204
+ function cohereEmbedding(model, config) {
205
+ return createCohereEmbedding(model, getCohereApiKeyFromEnv(), config);
206
+ }
207
+ //#endregion
208
+ export { CohereEmbeddingAdapter, cohereEmbedding, createCohereEmbedding };
209
+
210
+ //# sourceMappingURL=embedding.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Provider options for Cohere embedding models.
3
+ *
4
+ * `dimensions` is deliberately absent: it's a first-class top-level option on
5
+ * `embed()` and is mapped to Cohere's `output_dimension` request field by the
6
+ * adapter.
7
+ */
8
+ /**
9
+ * Provider options for `embed-v4.0`.
10
+ *
11
+ * `inputType` is required by Cohere's v2 embed API, which makes
12
+ * `modelOptions` required at the `embed()` call site.
13
+ */
14
+ export interface CohereEmbeddingProviderOptions {
15
+ /**
16
+ * The intended downstream use of the embeddings. Cohere requires this to
17
+ * pick the right embedding space:
18
+ * - `search_document` — corpus items stored for later retrieval
19
+ * - `search_query` — queries run against stored documents
20
+ * - `classification` — inputs embedded for classification tasks
21
+ * - `clustering` — inputs embedded for clustering tasks
22
+ */
23
+ inputType: 'search_document' | 'search_query' | 'classification' | 'clustering';
24
+ /**
25
+ * Requested embedding value encodings. The adapter always pins this to
26
+ * `['float']` so vectors are plain `number[]`; other encodings are not
27
+ * supported through TanStack AI.
28
+ */
29
+ embeddingTypes?: ['float'];
30
+ /**
31
+ * How to handle inputs longer than the model's maximum token length.
32
+ * `NONE` returns an error for over-long inputs; `START`/`END` truncate
33
+ * from the respective side. Defaults to Cohere's server-side default
34
+ * (`END`) when omitted.
35
+ */
36
+ truncate?: 'NONE' | 'START' | 'END';
37
+ }
@@ -1,3 +1,15 @@
1
+ /**
2
+ * @module @tanstack/ai-cohere
3
+ *
4
+ * Cohere provider adapter for TanStack AI.
5
+ * Provides tree-shakeable adapters for Cohere's v2/embed API (multimodal
6
+ * embeddings) and v2/rerank API (document reranking) using plain fetch —
7
+ * no SDK dependency.
8
+ */
9
+ export { CohereEmbeddingAdapter, createCohereEmbedding, cohereEmbedding, type CohereEmbeddingConfig, } from './adapters/embedding.js';
10
+ export type { CohereEmbeddingProviderOptions } from './embedding/embedding-provider-options.js';
1
11
  export { CohereRerankAdapter, createCohereRerank, cohereRerank, } from './adapters/rerank.js';
12
+ export { getCohereApiKeyFromEnv, type CohereClientConfig } from './utils/client.js';
13
+ export type { CohereEmbeddingModel, CohereEmbeddingModelProviderOptionsByName, CohereEmbeddingModelInputModalitiesByName, } from './model-meta.js';
14
+ export { COHERE_EMBEDDING_MODELS } from './model-meta.js';
2
15
  export { COHERE_RERANK_MODELS, type CohereRerankModel, type CohereRerankProviderOptions, type CohereRerankModelProviderOptionsByName, type InferCohereRerankProviderOptions, } from './model-meta.js';
3
- export type { CohereClientConfig } from './utils/client.js';
package/dist/esm/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { getCohereApiKeyFromEnv } from "./utils/client.js";
2
+ import { CohereEmbeddingAdapter, cohereEmbedding, createCohereEmbedding } from "./adapters/embedding.js";
1
3
  import { CohereRerankAdapter, cohereRerank, createCohereRerank } from "./adapters/rerank.js";
2
- import { COHERE_RERANK_MODELS } from "./model-meta.js";
3
- export { COHERE_RERANK_MODELS, CohereRerankAdapter, cohereRerank, createCohereRerank };
4
+ import { COHERE_EMBEDDING_MODELS, COHERE_RERANK_MODELS } from "./model-meta.js";
5
+ export { COHERE_EMBEDDING_MODELS, COHERE_RERANK_MODELS, CohereEmbeddingAdapter, CohereRerankAdapter, cohereEmbedding, cohereRerank, createCohereEmbedding, createCohereRerank, getCohereApiKeyFromEnv };
@@ -1,3 +1,26 @@
1
+ import { CohereEmbeddingProviderOptions } from './embedding/embedding-provider-options.js';
2
+ /**
3
+ * Embedding models (based on endpoints: "v2/embed")
4
+ */
5
+ export declare const COHERE_EMBEDDING_MODELS: readonly ["embed-v4.0"];
6
+ /**
7
+ * Union type of all supported Cohere embedding model names.
8
+ */
9
+ export type CohereEmbeddingModel = (typeof COHERE_EMBEDDING_MODELS)[number];
10
+ /**
11
+ * Type-only map from embedding model name to its provider options type.
12
+ */
13
+ export type CohereEmbeddingModelProviderOptionsByName = {
14
+ 'embed-v4.0': CohereEmbeddingProviderOptions;
15
+ };
16
+ /**
17
+ * Per-model input modalities for embedding models. embed-v4.0 is
18
+ * multimodal: it accepts text and image inputs (including fused
19
+ * text+image items that produce a single vector).
20
+ */
21
+ export type CohereEmbeddingModelInputModalitiesByName = {
22
+ 'embed-v4.0': readonly ['text', 'image'];
23
+ };
1
24
  /**
2
25
  * Cohere rerank model metadata.
3
26
  *
@@ -1,5 +1,9 @@
1
1
  //#region src/model-meta.ts
2
2
  /**
3
+ * Embedding models (based on endpoints: "v2/embed")
4
+ */
5
+ var COHERE_EMBEDDING_MODELS = ["embed-v4.0"];
6
+ /**
3
7
  * Cohere rerank model metadata.
4
8
  *
5
9
  * Provider options are resolved per model at the `cohereRerank('model')` call
@@ -15,6 +19,6 @@ var COHERE_RERANK_MODELS = [
15
19
  "rerank-multilingual-v3.0"
16
20
  ];
17
21
  //#endregion
18
- export { COHERE_RERANK_MODELS };
22
+ export { COHERE_EMBEDDING_MODELS, COHERE_RERANK_MODELS };
19
23
 
20
24
  //# sourceMappingURL=model-meta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-meta.js","names":[],"sources":["../../src/model-meta.ts"],"sourcesContent":["/**\n * Cohere rerank model metadata.\n *\n * Provider options are resolved per model at the `cohereRerank('model')` call\n * site via {@link CohereRerankModelProviderOptionsByName}. Cohere's rerank\n * models currently share the same options, but the per-model map keeps the\n * surface symmetric with the other adapters and lets divergent options be\n * expressed later without changing the adapter contract.\n */\n\n/** Available Cohere rerank models. */\nexport const COHERE_RERANK_MODELS = [\n 'rerank-v3.5',\n 'rerank-english-v3.0',\n 'rerank-multilingual-v3.0',\n] as const\n\n/** Union of supported Cohere rerank model names. */\nexport type CohereRerankModel = (typeof COHERE_RERANK_MODELS)[number]\n\n/**\n * Provider-specific options for a Cohere rerank request. Forwarded on the\n * `modelOptions` field of `rerank()`.\n */\nexport interface CohereRerankProviderOptions {\n /**\n * Long documents are chunked to fit the model's context. This caps the\n * number of tokens kept per document. Cohere defaults to 4096.\n */\n maxTokensPerDoc?: number\n}\n\n/**\n * Per-model provider-options map. Each model resolves to its own options type\n * at the factory call site (see {@link InferCohereRerankProviderOptions}).\n */\nexport interface CohereRerankModelProviderOptionsByName {\n 'rerank-v3.5': CohereRerankProviderOptions\n 'rerank-english-v3.0': CohereRerankProviderOptions\n 'rerank-multilingual-v3.0': CohereRerankProviderOptions\n}\n\n/**\n * Resolve the provider options for a given rerank model. Falls back to the\n * base options for any model not in the map.\n */\nexport type InferCohereRerankProviderOptions<TModel extends string> =\n TModel extends keyof CohereRerankModelProviderOptionsByName\n ? CohereRerankModelProviderOptionsByName[TModel]\n : CohereRerankProviderOptions\n"],"mappings":";;;;;;;;;;;AAWA,IAAa,uBAAuB;CAClC;CACA;CACA;AACF"}
1
+ {"version":3,"file":"model-meta.js","names":[],"sources":["../../src/model-meta.ts"],"sourcesContent":["import type { CohereEmbeddingProviderOptions } from './embedding/embedding-provider-options'\n\n/**\n * Embedding models (based on endpoints: \"v2/embed\")\n */\nexport const COHERE_EMBEDDING_MODELS = ['embed-v4.0'] as const\n\n/**\n * Union type of all supported Cohere embedding model names.\n */\nexport type CohereEmbeddingModel = (typeof COHERE_EMBEDDING_MODELS)[number]\n\n/**\n * Type-only map from embedding model name to its provider options type.\n */\nexport type CohereEmbeddingModelProviderOptionsByName = {\n 'embed-v4.0': CohereEmbeddingProviderOptions\n}\n\n/**\n * Per-model input modalities for embedding models. embed-v4.0 is\n * multimodal: it accepts text and image inputs (including fused\n * text+image items that produce a single vector).\n */\nexport type CohereEmbeddingModelInputModalitiesByName = {\n 'embed-v4.0': readonly ['text', 'image']\n}\n\n/**\n * Cohere rerank model metadata.\n *\n * Provider options are resolved per model at the `cohereRerank('model')` call\n * site via {@link CohereRerankModelProviderOptionsByName}. Cohere's rerank\n * models currently share the same options, but the per-model map keeps the\n * surface symmetric with the other adapters and lets divergent options be\n * expressed later without changing the adapter contract.\n */\n\n/** Available Cohere rerank models. */\nexport const COHERE_RERANK_MODELS = [\n 'rerank-v3.5',\n 'rerank-english-v3.0',\n 'rerank-multilingual-v3.0',\n] as const\n\n/** Union of supported Cohere rerank model names. */\nexport type CohereRerankModel = (typeof COHERE_RERANK_MODELS)[number]\n\n/**\n * Provider-specific options for a Cohere rerank request. Forwarded on the\n * `modelOptions` field of `rerank()`.\n */\nexport interface CohereRerankProviderOptions {\n /**\n * Long documents are chunked to fit the model's context. This caps the\n * number of tokens kept per document. Cohere defaults to 4096.\n */\n maxTokensPerDoc?: number\n}\n\n/**\n * Per-model provider-options map. Each model resolves to its own options type\n * at the factory call site (see {@link InferCohereRerankProviderOptions}).\n */\nexport interface CohereRerankModelProviderOptionsByName {\n 'rerank-v3.5': CohereRerankProviderOptions\n 'rerank-english-v3.0': CohereRerankProviderOptions\n 'rerank-multilingual-v3.0': CohereRerankProviderOptions\n}\n\n/**\n * Resolve the provider options for a given rerank model. Falls back to the\n * base options for any model not in the map.\n */\nexport type InferCohereRerankProviderOptions<TModel extends string> =\n TModel extends keyof CohereRerankModelProviderOptionsByName\n ? CohereRerankModelProviderOptionsByName[TModel]\n : CohereRerankProviderOptions\n"],"mappings":";;;;AAKA,IAAa,0BAA0B,CAAC,YAAY;;;;;;;;;;;AAkCpD,IAAa,uBAAuB;CAClC;CACA;CACA;AACF"}
@@ -1,21 +1,31 @@
1
1
  /**
2
- * Cohere client configuration shared by the rerank adapter.
2
+ * Configuration for the Cohere HTTP client used by the adapters in this
3
+ * package. Requests are made with plain `fetch` — no Cohere SDK dependency.
3
4
  */
4
5
  export interface CohereClientConfig {
5
- /** Cohere API key. Required by the adapter factories. */
6
+ /** Cohere API key. */
6
7
  apiKey: string;
7
- /** Override the API base URL. Defaults to `https://api.cohere.com`. */
8
+ /** Optional base URL override (defaults to `https://api.cohere.com`). */
8
9
  baseUrl?: string;
9
- /** Extra headers merged into every request. */
10
+ /** Optional default headers to include with every request. */
10
11
  headers?: Record<string, string>;
12
+ /**
13
+ * Cohere's embed API does not fetch remote image URLs itself. When this is
14
+ * enabled the adapter downloads http(s) image URLs and inlines them as
15
+ * base64 `data:` URIs before sending the request. Disabled by default.
16
+ */
17
+ allowUrlFetch?: boolean;
18
+ /** Request timeout in milliseconds for API and image URL fetches (default: 30_000). */
19
+ timeout?: number;
11
20
  }
12
21
  export declare const COHERE_DEFAULT_BASE_URL = "https://api.cohere.com";
13
22
  /**
14
- * Reads the Cohere API key from the environment.
23
+ * Gets Cohere API key from environment variables.
15
24
  *
16
- * Looks for `COHERE_API_KEY` in `process.env` (Node) or `window.env`
17
- * (browser with injected env).
25
+ * Looks for `COHERE_API_KEY` in:
26
+ * - `process.env` (Node.js)
27
+ * - `window.env` (Browser with injected env)
18
28
  *
19
- * @throws Error if `COHERE_API_KEY` is not found.
29
+ * @throws Error if COHERE_API_KEY is not found
20
30
  */
21
31
  export declare function getCohereApiKeyFromEnv(): string;
@@ -1,19 +1,17 @@
1
+ import { getApiKeyFromEnv } from "@tanstack/ai-utils";
1
2
  //#region src/utils/client.ts
2
3
  var COHERE_DEFAULT_BASE_URL = "https://api.cohere.com";
3
4
  /**
4
- * Reads the Cohere API key from the environment.
5
+ * Gets Cohere API key from environment variables.
5
6
  *
6
- * Looks for `COHERE_API_KEY` in `process.env` (Node) or `window.env`
7
- * (browser with injected env).
7
+ * Looks for `COHERE_API_KEY` in:
8
+ * - `process.env` (Node.js)
9
+ * - `window.env` (Browser with injected env)
8
10
  *
9
- * @throws Error if `COHERE_API_KEY` is not found.
11
+ * @throws Error if COHERE_API_KEY is not found
10
12
  */
11
13
  function getCohereApiKeyFromEnv() {
12
- const windowEnv = typeof globalThis !== "undefined" && globalThis.window ? globalThis.window.env : void 0;
13
- const processEnv = typeof process !== "undefined" ? process.env : void 0;
14
- const key = windowEnv?.["COHERE_API_KEY"] ?? processEnv?.["COHERE_API_KEY"];
15
- if (!key) throw new Error("COHERE_API_KEY not found in environment. Pass an API key explicitly via createCohereRerank(model, apiKey).");
16
- return key;
14
+ return getApiKeyFromEnv("COHERE_API_KEY");
17
15
  }
18
16
  //#endregion
19
17
  export { COHERE_DEFAULT_BASE_URL, getCohereApiKeyFromEnv };
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["/**\n * Cohere client configuration shared by the rerank adapter.\n */\nexport interface CohereClientConfig {\n /** Cohere API key. Required by the adapter factories. */\n apiKey: string\n /** Override the API base URL. Defaults to `https://api.cohere.com`. */\n baseUrl?: string\n /** Extra headers merged into every request. */\n headers?: Record<string, string>\n}\n\nexport const COHERE_DEFAULT_BASE_URL = 'https://api.cohere.com'\n\n/**\n * Reads the Cohere API key from the environment.\n *\n * Looks for `COHERE_API_KEY` in `process.env` (Node) or `window.env`\n * (browser with injected env).\n *\n * @throws Error if `COHERE_API_KEY` is not found.\n */\nexport function getCohereApiKeyFromEnv(): string {\n const windowEnv =\n typeof globalThis !== 'undefined' &&\n (globalThis as Record<string, unknown>).window\n ? ((\n (globalThis as Record<string, unknown>).window as Record<\n string,\n unknown\n >\n ).env as Record<string, string> | undefined)\n : undefined\n const processEnv = typeof process !== 'undefined' ? process.env : undefined\n // Prefer an injected `window.env` (browser builds) but fall back to\n // `process.env` — bundlers and Electron can populate it even when `window`\n // exists.\n const key = windowEnv?.['COHERE_API_KEY'] ?? processEnv?.['COHERE_API_KEY']\n if (!key) {\n throw new Error(\n 'COHERE_API_KEY not found in environment. Pass an API key explicitly via createCohereRerank(model, apiKey).',\n )\n }\n return key\n}\n"],"mappings":";AAYA,IAAa,0BAA0B;;;;;;;;;AAUvC,SAAgB,yBAAiC;CAC/C,MAAM,YACJ,OAAO,eAAe,eACrB,WAAuC,SAEjC,WAAuC,OAIxC,MACF,KAAA;CACN,MAAM,aAAa,OAAO,YAAY,cAAc,QAAQ,MAAM,KAAA;CAIlE,MAAM,MAAM,YAAY,qBAAqB,aAAa;CAC1D,IAAI,CAAC,KACH,MAAM,IAAI,MACR,4GACF;CAEF,OAAO;AACT"}
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"}
package/package.json CHANGED
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/ai-cohere",
3
- "version": "0.0.0",
4
- "publishConfig": {
5
- "access": "public"
6
- },
7
- "description": "Cohere adapter for TanStack AI — document reranking.",
3
+ "version": "0.1.0",
4
+ "description": "Cohere adapter for TanStack AI — multimodal embeddings and document reranking.",
8
5
  "author": "Tanner Linsley",
9
6
  "license": "MIT",
10
7
  "homepage": "https://tanstack.com/ai",
@@ -27,6 +24,10 @@
27
24
  ".": {
28
25
  "types": "./dist/esm/index.d.ts",
29
26
  "import": "./dist/esm/index.js"
27
+ },
28
+ "./adapters/embedding": {
29
+ "types": "./dist/esm/adapters/embedding.d.ts",
30
+ "import": "./dist/esm/adapters/embedding.js"
30
31
  }
31
32
  },
32
33
  "files": [
@@ -39,19 +40,24 @@
39
40
  "typescript",
40
41
  "tanstack",
41
42
  "cohere",
43
+ "adapter",
44
+ "embeddings",
45
+ "multimodal",
42
46
  "rerank",
43
47
  "reranking",
44
48
  "search",
45
- "retrieval",
46
- "adapter"
49
+ "retrieval"
47
50
  ],
48
51
  "peerDependencies": {
49
- "@tanstack/ai": "^0.43.1"
52
+ "@tanstack/ai": "^0.44.0"
50
53
  },
51
54
  "devDependencies": {
52
55
  "@vitest/coverage-v8": "4.0.14",
53
56
  "vite": "^8.1.4",
54
- "@tanstack/ai": "0.43.1"
57
+ "@tanstack/ai": "0.44.0"
58
+ },
59
+ "dependencies": {
60
+ "@tanstack/ai-utils": "^0.4.0"
55
61
  },
56
62
  "scripts": {
57
63
  "build": "vite build",
@@ -0,0 +1,369 @@
1
+ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters'
2
+ import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
3
+ import { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'
4
+ import { resolveEmbeddingInput } from '@tanstack/ai'
5
+ import { getCohereApiKeyFromEnv } from '../utils/client'
6
+ import type {
7
+ EmbeddingOptions,
8
+ EmbeddingResult,
9
+ ImagePart,
10
+ TokenUsage,
11
+ } from '@tanstack/ai'
12
+ import type {
13
+ CohereEmbeddingModel,
14
+ CohereEmbeddingModelInputModalitiesByName,
15
+ CohereEmbeddingModelProviderOptionsByName,
16
+ } from '../model-meta'
17
+ import type { CohereEmbeddingProviderOptions } from '../embedding/embedding-provider-options'
18
+ import type { CohereClientConfig } from '../utils/client'
19
+
20
+ /**
21
+ * Configuration for Cohere embedding adapter.
22
+ */
23
+ export interface CohereEmbeddingConfig extends CohereClientConfig {}
24
+
25
+ const DEFAULT_BASE_URL = 'https://api.cohere.com'
26
+ const DEFAULT_TIMEOUT_MS = 30_000
27
+
28
+ /**
29
+ * Returns true when `url` is malformed, non-http(s), or targets a private /
30
+ * loopback / link-local host. Used to block SSRF via `allowUrlFetch`.
31
+ */
32
+ function isPrivateOrInternalUrl(url: string): boolean {
33
+ let parsed: URL
34
+ try {
35
+ parsed = new URL(url)
36
+ } catch {
37
+ return true
38
+ }
39
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
40
+ return true
41
+ }
42
+ const host = parsed.hostname.toLowerCase()
43
+ if (
44
+ host === 'localhost' ||
45
+ host.endsWith('.localhost') ||
46
+ host === '::1' ||
47
+ host === '[::1]' ||
48
+ host.startsWith('127.') ||
49
+ host.startsWith('10.') ||
50
+ host.startsWith('192.168.') ||
51
+ host.startsWith('169.254.') ||
52
+ /^172\.(1[6-9]|2\d|3[01])\./.test(host)
53
+ ) {
54
+ return true
55
+ }
56
+ return false
57
+ }
58
+
59
+ async function fetchWithTimeout(
60
+ url: string,
61
+ init: RequestInit | undefined,
62
+ timeoutMs: number,
63
+ ): Promise<Response> {
64
+ const controller = new AbortController()
65
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
66
+ try {
67
+ return await fetch(url, { ...init, signal: controller.signal })
68
+ } finally {
69
+ clearTimeout(timeoutId)
70
+ }
71
+ }
72
+
73
+ /** One content part of a Cohere v2/embed fused input. */
74
+ type CohereEmbedContentPart =
75
+ | { type: 'text'; text: string }
76
+ | { type: 'image_url'; image_url: { url: string } }
77
+
78
+ /** Wire shape of the Cohere v2/embed request body. */
79
+ interface CohereEmbedRequestBody {
80
+ model: string
81
+ inputs: Array<{ content: Array<CohereEmbedContentPart> }>
82
+ input_type: CohereEmbeddingProviderOptions['inputType']
83
+ embedding_types: ['float']
84
+ truncate?: 'NONE' | 'START' | 'END'
85
+ output_dimension?: number
86
+ }
87
+
88
+ /** Wire shape of the Cohere v2/embed response (fields the adapter reads). */
89
+ interface CohereEmbedResponse {
90
+ id?: string
91
+ embeddings?: {
92
+ float?: Array<Array<number>>
93
+ }
94
+ meta?: {
95
+ billed_units?: {
96
+ input_tokens?: number
97
+ images?: number
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Cohere Embedding Adapter
104
+ *
105
+ * Tree-shakeable adapter for Cohere multimodal embeddings (embed-v4.0),
106
+ * implemented with plain `fetch` against the v2/embed endpoint — no Cohere
107
+ * SDK dependency.
108
+ *
109
+ * Features:
110
+ * - Batch embedding (one request for the whole input array)
111
+ * - Multimodal inputs: text, images, and fused text+image items (one vector
112
+ * per input item)
113
+ * - Matryoshka dimension reduction via the top-level `dimensions` option
114
+ * (mapped to Cohere's `output_dimension`)
115
+ */
116
+ export class CohereEmbeddingAdapter<
117
+ TModel extends CohereEmbeddingModel,
118
+ > extends BaseEmbeddingAdapter<
119
+ TModel,
120
+ CohereEmbeddingProviderOptions,
121
+ CohereEmbeddingModelProviderOptionsByName,
122
+ CohereEmbeddingModelInputModalitiesByName
123
+ > {
124
+ readonly name = 'cohere' as const
125
+
126
+ protected clientConfig: CohereEmbeddingConfig
127
+
128
+ constructor(config: CohereEmbeddingConfig, model: TModel) {
129
+ super(model, {})
130
+ this.clientConfig = config
131
+ }
132
+
133
+ async createEmbeddings(
134
+ options: EmbeddingOptions<CohereEmbeddingProviderOptions>,
135
+ ): Promise<EmbeddingResult> {
136
+ const { model, logger, modelOptions } = options
137
+
138
+ try {
139
+ // The provider options type makes `modelOptions` required at the
140
+ // embed() call site; this guard covers untyped/dynamic callers.
141
+ const inputType: CohereEmbeddingProviderOptions['inputType'] | undefined =
142
+ modelOptions?.inputType
143
+ if (!inputType) {
144
+ throw new Error(
145
+ `Cohere embeddings require modelOptions.inputType ('search_document' | 'search_query' | 'classification' | 'clustering').`,
146
+ )
147
+ }
148
+
149
+ const resolved = resolveEmbeddingInput(options.input)
150
+ const inputs = await Promise.all(
151
+ resolved.map(async (item) => {
152
+ const content: Array<CohereEmbedContentPart> = item.texts.map(
153
+ (text) => ({ type: 'text', text }),
154
+ )
155
+ for (const image of item.images) {
156
+ content.push({
157
+ type: 'image_url',
158
+ image_url: { url: await this.resolveImageUrl(image) },
159
+ })
160
+ }
161
+ return { content }
162
+ }),
163
+ )
164
+
165
+ // embedding_types is pinned to ['float'] (overriding any disagreeing
166
+ // modelOptions.embeddingTypes) so vectors are always number[].
167
+ const body: CohereEmbedRequestBody = {
168
+ model,
169
+ inputs,
170
+ input_type: inputType,
171
+ embedding_types: ['float'],
172
+ }
173
+ const truncate = modelOptions?.truncate
174
+ if (truncate !== undefined) {
175
+ body.truncate = truncate
176
+ }
177
+ if (options.dimensions !== undefined) {
178
+ body.output_dimension = options.dimensions
179
+ }
180
+
181
+ logger.request(
182
+ `activity=embed provider=${this.name} model=${model} inputs=${inputs.length}`,
183
+ { provider: this.name, model },
184
+ )
185
+
186
+ const timeoutMs = this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS
187
+ const response = await fetchWithTimeout(
188
+ `${this.clientConfig.baseUrl ?? DEFAULT_BASE_URL}/v2/embed`,
189
+ {
190
+ method: 'POST',
191
+ headers: {
192
+ Authorization: `Bearer ${this.clientConfig.apiKey}`,
193
+ 'Content-Type': 'application/json',
194
+ ...this.clientConfig.headers,
195
+ },
196
+ body: JSON.stringify(body),
197
+ },
198
+ timeoutMs,
199
+ )
200
+
201
+ if (!response.ok) {
202
+ const bodyText = await response.text()
203
+ let message = bodyText
204
+ try {
205
+ const parsed: unknown = JSON.parse(bodyText)
206
+ if (
207
+ typeof parsed === 'object' &&
208
+ parsed !== null &&
209
+ 'message' in parsed &&
210
+ typeof parsed.message === 'string'
211
+ ) {
212
+ message = parsed.message
213
+ }
214
+ } catch {
215
+ // Not JSON — fall back to the raw body text.
216
+ }
217
+ throw new Error(`Cohere embed failed (${response.status}): ${message}`)
218
+ }
219
+
220
+ const data = (await response.json()) as CohereEmbedResponse
221
+
222
+ const vectors = data.embeddings?.float
223
+ if (!vectors) {
224
+ throw new Error(
225
+ 'Cohere embed response did not include float embeddings',
226
+ )
227
+ }
228
+ if (vectors.length !== inputs.length) {
229
+ throw new Error(
230
+ `Cohere embed returned ${vectors.length} embeddings for ${inputs.length} inputs`,
231
+ )
232
+ }
233
+
234
+ const result: EmbeddingResult = {
235
+ id: generateId(this.name),
236
+ model,
237
+ embeddings: vectors.map((vector, index) => ({ vector, index })),
238
+ }
239
+
240
+ const inputTokens = data.meta?.billed_units?.input_tokens
241
+ if (inputTokens !== undefined) {
242
+ const usage: TokenUsage = {
243
+ promptTokens: inputTokens,
244
+ completionTokens: 0,
245
+ totalTokens: inputTokens,
246
+ }
247
+ result.usage = usage
248
+ }
249
+
250
+ return result
251
+ } catch (error: unknown) {
252
+ logger.errors(`${this.name}.createEmbeddings fatal`, {
253
+ error: toRunErrorPayload(error, `${this.name}.createEmbeddings failed`),
254
+ source: `${this.name}.createEmbeddings`,
255
+ })
256
+ throw error
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Resolves an image part to a URL Cohere accepts. Cohere does not fetch
262
+ * remote image URLs, so everything is normalized to a `data:` URI unless
263
+ * the caller already provided one.
264
+ */
265
+ protected async resolveImageUrl(image: ImagePart): Promise<string> {
266
+ const source = image.source
267
+
268
+ if (source.type === 'data') {
269
+ return `data:${source.mimeType};base64,${source.value}`
270
+ }
271
+
272
+ if (source.value.startsWith('data:')) {
273
+ return source.value
274
+ }
275
+
276
+ if (!this.clientConfig.allowUrlFetch) {
277
+ throw new Error(
278
+ 'Cohere does not fetch remote image URLs; pass base64 data or a data: URI (or enable config.allowUrlFetch to have the adapter download it)',
279
+ )
280
+ }
281
+
282
+ if (isPrivateOrInternalUrl(source.value)) {
283
+ throw new Error(
284
+ `Refusing to fetch internal or private URL for Cohere embedding: ${source.value}`,
285
+ )
286
+ }
287
+
288
+ const response = await fetchWithTimeout(
289
+ source.value,
290
+ undefined,
291
+ this.clientConfig.timeout ?? DEFAULT_TIMEOUT_MS,
292
+ )
293
+ if (!response.ok) {
294
+ throw new Error(
295
+ `Failed to fetch image URL for Cohere embedding (${response.status}): ${source.value}`,
296
+ )
297
+ }
298
+ const mimeType =
299
+ response.headers.get('content-type') ??
300
+ source.mimeType ??
301
+ 'application/octet-stream'
302
+ const base64 = arrayBufferToBase64(await response.arrayBuffer())
303
+ return `data:${mimeType};base64,${base64}`
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Creates a Cohere embedding adapter with explicit API key.
309
+ * Type resolution happens here at the call site.
310
+ *
311
+ * @param model - The model name (e.g., 'embed-v4.0')
312
+ * @param apiKey - Your Cohere API key
313
+ * @param config - Optional additional configuration
314
+ * @returns Configured Cohere embedding adapter instance with resolved types
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * const adapter = createCohereEmbedding('embed-v4.0', 'api_key');
319
+ *
320
+ * const result = await embed({
321
+ * adapter,
322
+ * input: 'a red guitar',
323
+ * modelOptions: { inputType: 'search_document' }
324
+ * });
325
+ * ```
326
+ */
327
+ export function createCohereEmbedding<TModel extends CohereEmbeddingModel>(
328
+ model: TModel,
329
+ apiKey: string,
330
+ config?: Omit<CohereEmbeddingConfig, 'apiKey'>,
331
+ ): CohereEmbeddingAdapter<TModel> {
332
+ return new CohereEmbeddingAdapter({ apiKey, ...config }, model)
333
+ }
334
+
335
+ /**
336
+ * Creates a Cohere embedding adapter using the `COHERE_API_KEY` environment variable.
337
+ * Type resolution happens here at the call site.
338
+ *
339
+ * Looks for `COHERE_API_KEY` in:
340
+ * - `process.env` (Node.js)
341
+ * - `window.env` (Browser with injected env)
342
+ *
343
+ * @param model - The model name (e.g., 'embed-v4.0')
344
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
345
+ * @returns Configured Cohere embedding adapter instance with resolved types
346
+ * @throws Error if COHERE_API_KEY is not found in environment
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * // Automatically uses COHERE_API_KEY from environment
351
+ * const adapter = cohereEmbedding('embed-v4.0');
352
+ *
353
+ * const result = await embed({
354
+ * adapter,
355
+ * input: ['a red guitar', 'a blue drum kit'],
356
+ * modelOptions: { inputType: 'search_query' },
357
+ * dimensions: 1024
358
+ * });
359
+ *
360
+ * console.log(result.embeddings[0].vector)
361
+ * ```
362
+ */
363
+ export function cohereEmbedding<TModel extends CohereEmbeddingModel>(
364
+ model: TModel,
365
+ config?: Omit<CohereEmbeddingConfig, 'apiKey'>,
366
+ ): CohereEmbeddingAdapter<TModel> {
367
+ const apiKey = getCohereApiKeyFromEnv()
368
+ return createCohereEmbedding(model, apiKey, config)
369
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Provider options for Cohere embedding models.
3
+ *
4
+ * `dimensions` is deliberately absent: it's a first-class top-level option on
5
+ * `embed()` and is mapped to Cohere's `output_dimension` request field by the
6
+ * adapter.
7
+ */
8
+
9
+ /**
10
+ * Provider options for `embed-v4.0`.
11
+ *
12
+ * `inputType` is required by Cohere's v2 embed API, which makes
13
+ * `modelOptions` required at the `embed()` call site.
14
+ */
15
+ export interface CohereEmbeddingProviderOptions {
16
+ /**
17
+ * The intended downstream use of the embeddings. Cohere requires this to
18
+ * pick the right embedding space:
19
+ * - `search_document` — corpus items stored for later retrieval
20
+ * - `search_query` — queries run against stored documents
21
+ * - `classification` — inputs embedded for classification tasks
22
+ * - `clustering` — inputs embedded for clustering tasks
23
+ */
24
+ inputType:
25
+ | 'search_document'
26
+ | 'search_query'
27
+ | 'classification'
28
+ | 'clustering'
29
+
30
+ /**
31
+ * Requested embedding value encodings. The adapter always pins this to
32
+ * `['float']` so vectors are plain `number[]`; other encodings are not
33
+ * supported through TanStack AI.
34
+ */
35
+ embeddingTypes?: ['float']
36
+
37
+ /**
38
+ * How to handle inputs longer than the model's maximum token length.
39
+ * `NONE` returns an error for over-long inputs; `START`/`END` truncate
40
+ * from the respective side. Defaults to Cohere's server-side default
41
+ * (`END`) when omitted.
42
+ */
43
+ truncate?: 'NONE' | 'START' | 'END'
44
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,25 @@
1
+ /**
2
+ * @module @tanstack/ai-cohere
3
+ *
4
+ * Cohere provider adapter for TanStack AI.
5
+ * Provides tree-shakeable adapters for Cohere's v2/embed API (multimodal
6
+ * embeddings) and v2/rerank API (document reranking) using plain fetch —
7
+ * no SDK dependency.
8
+ */
9
+
1
10
  // ============================================================================
2
11
  // Cohere Adapters (tree-shakeable)
3
12
  // ============================================================================
4
13
 
14
+ // Embedding adapter - for embedding vectors
15
+ export {
16
+ CohereEmbeddingAdapter,
17
+ createCohereEmbedding,
18
+ cohereEmbedding,
19
+ type CohereEmbeddingConfig,
20
+ } from './adapters/embedding'
21
+ export type { CohereEmbeddingProviderOptions } from './embedding/embedding-provider-options'
22
+
5
23
  // Rerank adapter - document reranking via Cohere's /v2/rerank endpoint
6
24
  export {
7
25
  CohereRerankAdapter,
@@ -9,10 +27,20 @@ export {
9
27
  cohereRerank,
10
28
  } from './adapters/rerank'
11
29
 
30
+ // Client config + env helpers
31
+ export { getCohereApiKeyFromEnv, type CohereClientConfig } from './utils/client'
32
+
12
33
  // ============================================================================
13
34
  // Type Exports
14
35
  // ============================================================================
15
36
 
37
+ export type {
38
+ CohereEmbeddingModel,
39
+ CohereEmbeddingModelProviderOptionsByName,
40
+ CohereEmbeddingModelInputModalitiesByName,
41
+ } from './model-meta'
42
+ export { COHERE_EMBEDDING_MODELS } from './model-meta'
43
+
16
44
  export {
17
45
  COHERE_RERANK_MODELS,
18
46
  type CohereRerankModel,
@@ -20,5 +48,3 @@ export {
20
48
  type CohereRerankModelProviderOptionsByName,
21
49
  type InferCohereRerankProviderOptions,
22
50
  } from './model-meta'
23
-
24
- export type { CohereClientConfig } from './utils/client'
package/src/model-meta.ts CHANGED
@@ -1,3 +1,31 @@
1
+ import type { CohereEmbeddingProviderOptions } from './embedding/embedding-provider-options'
2
+
3
+ /**
4
+ * Embedding models (based on endpoints: "v2/embed")
5
+ */
6
+ export const COHERE_EMBEDDING_MODELS = ['embed-v4.0'] as const
7
+
8
+ /**
9
+ * Union type of all supported Cohere embedding model names.
10
+ */
11
+ export type CohereEmbeddingModel = (typeof COHERE_EMBEDDING_MODELS)[number]
12
+
13
+ /**
14
+ * Type-only map from embedding model name to its provider options type.
15
+ */
16
+ export type CohereEmbeddingModelProviderOptionsByName = {
17
+ 'embed-v4.0': CohereEmbeddingProviderOptions
18
+ }
19
+
20
+ /**
21
+ * Per-model input modalities for embedding models. embed-v4.0 is
22
+ * multimodal: it accepts text and image inputs (including fused
23
+ * text+image items that produce a single vector).
24
+ */
25
+ export type CohereEmbeddingModelInputModalitiesByName = {
26
+ 'embed-v4.0': readonly ['text', 'image']
27
+ }
28
+
1
29
  /**
2
30
  * Cohere rerank model metadata.
3
31
  *
@@ -1,45 +1,41 @@
1
+ import { getApiKeyFromEnv } from '@tanstack/ai-utils'
2
+
1
3
  /**
2
- * Cohere client configuration shared by the rerank adapter.
4
+ * Configuration for the Cohere HTTP client used by the adapters in this
5
+ * package. Requests are made with plain `fetch` — no Cohere SDK dependency.
3
6
  */
4
7
  export interface CohereClientConfig {
5
- /** Cohere API key. Required by the adapter factories. */
8
+ /** Cohere API key. */
6
9
  apiKey: string
7
- /** Override the API base URL. Defaults to `https://api.cohere.com`. */
10
+
11
+ /** Optional base URL override (defaults to `https://api.cohere.com`). */
8
12
  baseUrl?: string
9
- /** Extra headers merged into every request. */
13
+
14
+ /** Optional default headers to include with every request. */
10
15
  headers?: Record<string, string>
16
+
17
+ /**
18
+ * Cohere's embed API does not fetch remote image URLs itself. When this is
19
+ * enabled the adapter downloads http(s) image URLs and inlines them as
20
+ * base64 `data:` URIs before sending the request. Disabled by default.
21
+ */
22
+ allowUrlFetch?: boolean
23
+
24
+ /** Request timeout in milliseconds for API and image URL fetches (default: 30_000). */
25
+ timeout?: number
11
26
  }
12
27
 
13
28
  export const COHERE_DEFAULT_BASE_URL = 'https://api.cohere.com'
14
29
 
15
30
  /**
16
- * Reads the Cohere API key from the environment.
31
+ * Gets Cohere API key from environment variables.
17
32
  *
18
- * Looks for `COHERE_API_KEY` in `process.env` (Node) or `window.env`
19
- * (browser with injected env).
33
+ * Looks for `COHERE_API_KEY` in:
34
+ * - `process.env` (Node.js)
35
+ * - `window.env` (Browser with injected env)
20
36
  *
21
- * @throws Error if `COHERE_API_KEY` is not found.
37
+ * @throws Error if COHERE_API_KEY is not found
22
38
  */
23
39
  export function getCohereApiKeyFromEnv(): string {
24
- const windowEnv =
25
- typeof globalThis !== 'undefined' &&
26
- (globalThis as Record<string, unknown>).window
27
- ? ((
28
- (globalThis as Record<string, unknown>).window as Record<
29
- string,
30
- unknown
31
- >
32
- ).env as Record<string, string> | undefined)
33
- : undefined
34
- const processEnv = typeof process !== 'undefined' ? process.env : undefined
35
- // Prefer an injected `window.env` (browser builds) but fall back to
36
- // `process.env` — bundlers and Electron can populate it even when `window`
37
- // exists.
38
- const key = windowEnv?.['COHERE_API_KEY'] ?? processEnv?.['COHERE_API_KEY']
39
- if (!key) {
40
- throw new Error(
41
- 'COHERE_API_KEY not found in environment. Pass an API key explicitly via createCohereRerank(model, apiKey).',
42
- )
43
- }
44
- return key
40
+ return getApiKeyFromEnv('COHERE_API_KEY')
45
41
  }