@tanstack/ai-cohere 0.0.0 → 0.1.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 +107 -8
- package/dist/esm/adapters/embedding.d.ts +86 -0
- package/dist/esm/adapters/embedding.js +210 -0
- package/dist/esm/adapters/embedding.js.map +1 -0
- package/dist/esm/embedding/embedding-provider-options.d.ts +37 -0
- package/dist/esm/index.d.ts +13 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/model-meta.d.ts +23 -0
- package/dist/esm/model-meta.js +5 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/utils/client.d.ts +18 -8
- package/dist/esm/utils/client.js +7 -9
- package/dist/esm/utils/client.js.map +1 -1
- package/package.json +17 -11
- package/src/adapters/embedding.ts +369 -0
- package/src/embedding/embedding-provider-options.ts +44 -0
- package/src/index.ts +28 -2
- package/src/model-meta.ts +28 -0
- package/src/utils/client.ts +25 -29
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).
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/esm/model-meta.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/esm/model-meta.js
CHANGED
|
@@ -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":";;;;;;;;;;;
|
|
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
|
|
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.
|
|
6
|
+
/** Cohere API key. */
|
|
6
7
|
apiKey: string;
|
|
7
|
-
/**
|
|
8
|
+
/** Optional base URL override (defaults to `https://api.cohere.com`). */
|
|
8
9
|
baseUrl?: string;
|
|
9
|
-
/**
|
|
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
|
-
*
|
|
23
|
+
* Gets Cohere API key from environment variables.
|
|
15
24
|
*
|
|
16
|
-
* Looks for `COHERE_API_KEY` in
|
|
17
|
-
*
|
|
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
|
|
29
|
+
* @throws Error if COHERE_API_KEY is not found
|
|
20
30
|
*/
|
|
21
31
|
export declare function getCohereApiKeyFromEnv(): string;
|
package/dist/esm/utils/client.js
CHANGED
|
@@ -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
|
-
*
|
|
5
|
+
* Gets Cohere API key from environment variables.
|
|
5
6
|
*
|
|
6
|
-
* Looks for `COHERE_API_KEY` in
|
|
7
|
-
*
|
|
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
|
|
11
|
+
* @throws Error if COHERE_API_KEY is not found
|
|
10
12
|
*/
|
|
11
13
|
function getCohereApiKeyFromEnv() {
|
|
12
|
-
|
|
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
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"access": "public"
|
|
6
|
-
},
|
|
7
|
-
"description": "Cohere adapter for TanStack AI — document reranking.",
|
|
3
|
+
"version": "0.1.1",
|
|
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.
|
|
52
|
+
"@tanstack/ai": "^0.45.0"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
52
|
-
"@vitest/coverage-v8": "4.
|
|
53
|
-
"vite": "^8.1
|
|
54
|
-
"@tanstack/ai": "0.
|
|
55
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
56
|
+
"vite": "^8.2.1",
|
|
57
|
+
"@tanstack/ai": "0.45.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
|
*
|
package/src/utils/client.ts
CHANGED
|
@@ -1,45 +1,41 @@
|
|
|
1
|
+
import { getApiKeyFromEnv } from '@tanstack/ai-utils'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Cohere client
|
|
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.
|
|
8
|
+
/** Cohere API key. */
|
|
6
9
|
apiKey: string
|
|
7
|
-
|
|
10
|
+
|
|
11
|
+
/** Optional base URL override (defaults to `https://api.cohere.com`). */
|
|
8
12
|
baseUrl?: string
|
|
9
|
-
|
|
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
|
-
*
|
|
31
|
+
* Gets Cohere API key from environment variables.
|
|
17
32
|
*
|
|
18
|
-
* Looks for `COHERE_API_KEY` in
|
|
19
|
-
*
|
|
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
|
|
37
|
+
* @throws Error if COHERE_API_KEY is not found
|
|
22
38
|
*/
|
|
23
39
|
export function getCohereApiKeyFromEnv(): string {
|
|
24
|
-
|
|
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
|
}
|