rosetta-ai 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +306 -71
- package/dist/index.cjs +409 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +782 -6
- package/dist/index.d.ts +782 -6
- package/dist/index.js +409 -141
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
The translation layer for LLM provider messages.
|
|
4
4
|
|
|
5
|
-
Rosetta converts messages between different LLM providers using a standardized intermediate format
|
|
5
|
+
Rosetta converts messages between different LLM providers using [**GenAI**](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/), a standardized intermediate format. Just pass in messages from any provider—OpenAI, Anthropic, Google, or even custom formats—and get consistent output. No manual mapping required.
|
|
6
|
+
|
|
7
|
+
> Rosetta was made by [Latitude](https://latitude.so?utm_source=github&utm_medium=oss&utm_campaign=rosetta_ai) as an effort to standardize the observability layer for any LLM application!
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
@@ -29,92 +31,265 @@ yarn add rosetta-ai
|
|
|
29
31
|
## Quick Start
|
|
30
32
|
|
|
31
33
|
```typescript
|
|
32
|
-
import { translate
|
|
34
|
+
import { translate } from "rosetta-ai";
|
|
33
35
|
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
{ role: "
|
|
36
|
+
// Translate any LLM messages - provider is auto-detected
|
|
37
|
+
const openAIMessages = [
|
|
38
|
+
{ role: "system", content: "You are a helpful assistant." },
|
|
39
|
+
{ role: "user", content: "Hello!" },
|
|
40
|
+
{ role: "assistant", content: "Hi there! How can I help you today?" },
|
|
37
41
|
];
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const { messages, system } = translate(openAIMessages);
|
|
44
|
+
// messages: GenAI format messages (user + assistant)
|
|
45
|
+
// system: extracted system instructions
|
|
46
|
+
```
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
48
|
+
Works with messages from any provider:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// OpenAI Chat Completions
|
|
52
|
+
const openAI = [{ role: "user", content: "Hello" }];
|
|
53
|
+
translate(openAI); // Just works
|
|
54
|
+
|
|
55
|
+
// Anthropic
|
|
56
|
+
const anthropic = [{ role: "user", content: [{ type: "text", text: "Hello" }] }];
|
|
57
|
+
translate(anthropic); // Just works
|
|
58
|
+
|
|
59
|
+
// Vercel AI SDK
|
|
60
|
+
const vercelAI = [{ role: "user", content: "Hello" }];
|
|
61
|
+
translate(vercelAI); // Just works
|
|
62
|
+
|
|
63
|
+
// More providers...
|
|
64
|
+
|
|
65
|
+
// Unknown provider? Also works (uses Compat fallback)
|
|
66
|
+
const unknown = [{ role: "user", content: "Hello" }];
|
|
67
|
+
translate(unknown); // Still works
|
|
47
68
|
```
|
|
48
69
|
|
|
49
70
|
## API
|
|
50
71
|
|
|
51
|
-
### translate
|
|
72
|
+
### translate
|
|
73
|
+
|
|
74
|
+
The main function for translating messages between providers.
|
|
52
75
|
|
|
53
76
|
```typescript
|
|
54
|
-
import { translate,
|
|
77
|
+
import { translate, Provider } from "rosetta-ai";
|
|
55
78
|
|
|
56
|
-
// translate throws on error
|
|
57
79
|
const { messages, system } = translate(inputMessages, {
|
|
58
|
-
from: Provider.
|
|
59
|
-
to: Provider.GenAI,
|
|
60
|
-
system:
|
|
61
|
-
direction: "input",
|
|
80
|
+
from: Provider.OpenAICompletions, // Source provider (optional, auto-detected if omitted)
|
|
81
|
+
to: Provider.GenAI, // Target provider (optional, defaults to GenAI)
|
|
82
|
+
system: "You are helpful", // Separated system instructions (optional)
|
|
83
|
+
direction: "input", // "input" (default) or "output"
|
|
62
84
|
});
|
|
85
|
+
```
|
|
63
86
|
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
**Options:**
|
|
88
|
+
|
|
89
|
+
| Option | Type | Default | Description |
|
|
90
|
+
|--------|------|---------|-------------|
|
|
91
|
+
| `from` | `Provider` | auto-detected | Source provider format |
|
|
92
|
+
| `to` | `Provider` | `Provider.GenAI` | Target provider format |
|
|
93
|
+
| `system` | `string \| object \| object[]` | - | System instructions (for providers that separate them) |
|
|
94
|
+
| `direction` | `"input" \| "output"` | `"input"` | Affects role interpretation when translating strings |
|
|
95
|
+
|
|
96
|
+
**Returns:** `{ messages, system? }` - translated messages and optional system instructions
|
|
97
|
+
|
|
98
|
+
### safeTranslate
|
|
99
|
+
|
|
100
|
+
Same as `translate`, but returns an error object instead of throwing.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { safeTranslate } from "rosetta-ai";
|
|
104
|
+
|
|
105
|
+
const result = safeTranslate(messages, options);
|
|
66
106
|
|
|
67
107
|
if (result.error) {
|
|
68
|
-
|
|
108
|
+
// Handle error: result.error is Error
|
|
69
109
|
} else {
|
|
70
|
-
|
|
110
|
+
// Use result.messages (properly typed)
|
|
71
111
|
}
|
|
72
112
|
```
|
|
73
113
|
|
|
114
|
+
### Translator Class
|
|
115
|
+
|
|
116
|
+
For advanced configuration, create a `Translator` instance:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Translator, Provider } from "rosetta-ai";
|
|
120
|
+
|
|
121
|
+
const translator = new Translator({
|
|
122
|
+
// Custom priority order for provider auto-detection
|
|
123
|
+
inferPriority: [Provider.OpenAICompletions, Provider.Anthropic, Provider.GenAI],
|
|
124
|
+
|
|
125
|
+
// Filter out empty messages during translation (default: false)
|
|
126
|
+
filterEmptyMessages: true,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const { messages } = translator.translate(inputMessages);
|
|
130
|
+
const safeResult = translator.safeTranslate(inputMessages);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Configuration Options:**
|
|
134
|
+
|
|
135
|
+
| Option | Type | Default | Description |
|
|
136
|
+
|--------|------|---------|-------------|
|
|
137
|
+
| `inferPriority` | `Provider[]` | `DEFAULT_INFER_PRIORITY` | Priority order for provider auto-detection |
|
|
138
|
+
| `filterEmptyMessages` | `boolean` | `false` | Remove empty messages (no parts, or only empty text) during translation |
|
|
139
|
+
|
|
74
140
|
### Input Flexibility
|
|
75
141
|
|
|
76
|
-
Messages and system instructions accept flexible
|
|
142
|
+
Messages and system instructions accept flexible formats:
|
|
77
143
|
|
|
78
144
|
```typescript
|
|
79
|
-
// Messages: string or array
|
|
80
|
-
translate("Hello!"); //
|
|
81
|
-
translate([{ role: "user", content: "Hello!" }]); //
|
|
145
|
+
// Messages: string or array
|
|
146
|
+
translate("Hello!"); // String → single message
|
|
147
|
+
translate([{ role: "user", content: "Hello!" }]); // Array of provider messages
|
|
82
148
|
|
|
83
|
-
// System: string,
|
|
149
|
+
// System: string, object, or array
|
|
84
150
|
translate(messages, { system: "You are helpful" });
|
|
85
151
|
translate(messages, { system: { type: "text", content: "Be helpful" } });
|
|
86
|
-
translate(messages, { system: [{ type: "text", content: "
|
|
152
|
+
translate(messages, { system: [{ type: "text", content: "Part 1" }, { type: "text", content: "Part 2" }] });
|
|
87
153
|
```
|
|
88
154
|
|
|
89
|
-
|
|
155
|
+
## Common Use Cases
|
|
90
156
|
|
|
91
|
-
###
|
|
157
|
+
### Translate API responses for storage or display
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import OpenAI from "openai";
|
|
161
|
+
import { translate, Provider } from "rosetta-ai";
|
|
162
|
+
|
|
163
|
+
const openai = new OpenAI();
|
|
164
|
+
const completion = await openai.chat.completions.create({
|
|
165
|
+
model: "gpt-4o",
|
|
166
|
+
messages: [{ role: "user", content: "What's the weather?" }],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Translate OpenAI response to unified GenAI format
|
|
170
|
+
const { messages } = translate([completion.choices[0].message], {
|
|
171
|
+
from: Provider.OpenAICompletions,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Now you have a consistent format regardless of which provider you used
|
|
175
|
+
console.log(messages[0].parts[0]); // { type: "text", content: "..." }
|
|
176
|
+
```
|
|
92
177
|
|
|
93
|
-
|
|
178
|
+
### Cross-provider translation
|
|
94
179
|
|
|
95
180
|
```typescript
|
|
96
|
-
import {
|
|
181
|
+
import { translate, Provider } from "rosetta-ai";
|
|
97
182
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
183
|
+
// Translate OpenAI messages to Vercel AI SDK format
|
|
184
|
+
const openAIMessages = [
|
|
185
|
+
{ role: "system", content: "You are helpful." },
|
|
186
|
+
{ role: "user", content: "Hello!" },
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
const { messages } = translate(openAIMessages, {
|
|
190
|
+
from: Provider.OpenAICompletions,
|
|
191
|
+
to: Provider.VercelAI,
|
|
101
192
|
});
|
|
193
|
+
// Result: Vercel AI SDK compatible messages
|
|
194
|
+
```
|
|
102
195
|
|
|
103
|
-
|
|
196
|
+
### Handle tool calls across providers
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { translate, Provider } from "rosetta-ai";
|
|
200
|
+
|
|
201
|
+
// OpenAI tool call format
|
|
202
|
+
const openAIWithToolCall = [
|
|
203
|
+
{
|
|
204
|
+
role: "assistant",
|
|
205
|
+
content: null,
|
|
206
|
+
tool_calls: [{
|
|
207
|
+
id: "call_abc123",
|
|
208
|
+
type: "function",
|
|
209
|
+
function: { name: "get_weather", arguments: '{"location":"Paris"}' },
|
|
210
|
+
}],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
role: "tool",
|
|
214
|
+
tool_call_id: "call_abc123",
|
|
215
|
+
content: '{"temp": 20}',
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
// Translates to unified GenAI format with tool_call and tool_call_response parts
|
|
220
|
+
const { messages } = translate(openAIWithToolCall, {
|
|
221
|
+
from: Provider.OpenAICompletions,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Tool call part
|
|
225
|
+
messages[0].parts[0]; // { type: "tool_call", name: "get_weather", arguments: { location: "Paris" }, ... }
|
|
226
|
+
|
|
227
|
+
// Tool response part
|
|
228
|
+
messages[1].parts[0]; // { type: "tool_call_response", call_id: "call_abc123", content: {...}, ... }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Translate multimodal content
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { translate, Provider } from "rosetta-ai";
|
|
235
|
+
|
|
236
|
+
const anthropicWithImage = [
|
|
237
|
+
{
|
|
238
|
+
role: "user",
|
|
239
|
+
content: [
|
|
240
|
+
{ type: "text", text: "What's in this image?" },
|
|
241
|
+
{
|
|
242
|
+
type: "image",
|
|
243
|
+
source: {
|
|
244
|
+
type: "base64",
|
|
245
|
+
media_type: "image/png",
|
|
246
|
+
data: "iVBORw0KGgo...",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const { messages } = translate(anthropicWithImage, {
|
|
254
|
+
from: Provider.Anthropic,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Image converted to blob part
|
|
258
|
+
messages[0].parts[1]; // { type: "blob", modality: "image", mime_type: "image/png", content: "..." }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Safe translation with error handling
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { safeTranslate } from "rosetta-ai";
|
|
265
|
+
|
|
266
|
+
const result = safeTranslate(unknownMessages);
|
|
267
|
+
|
|
268
|
+
if (result.error) {
|
|
269
|
+
console.error("Translation failed:", result.error.message);
|
|
270
|
+
} else {
|
|
271
|
+
console.log("Translated:", result.messages);
|
|
272
|
+
}
|
|
104
273
|
```
|
|
105
274
|
|
|
275
|
+
|
|
276
|
+
|
|
106
277
|
## Supported Providers
|
|
107
278
|
|
|
108
|
-
| Provider
|
|
109
|
-
|
|
110
|
-
| GenAI
|
|
111
|
-
| Promptl
|
|
112
|
-
|
|
|
113
|
-
| OpenAI Completions | ✅
|
|
114
|
-
| OpenAI Responses
|
|
115
|
-
| Anthropic
|
|
116
|
-
| Google Gemini
|
|
117
|
-
| Compat
|
|
279
|
+
| Provider | toGenAI | fromGenAI | Separated System | Description |
|
|
280
|
+
|----------|---------|-----------|-----------------|-------------|
|
|
281
|
+
| GenAI | ✅ | ✅ | Optional | Intermediate format (default target) |
|
|
282
|
+
| Promptl | ✅ | ✅ | - | [promptl-ai](https://github.com/latitude-dev/promptl) format |
|
|
283
|
+
| Vercel AI | ✅ | ✅ | - | Vercel AI SDK messages |
|
|
284
|
+
| OpenAI Completions | ✅ | - | - | Chat Completions API |
|
|
285
|
+
| OpenAI Responses | ✅ | - | - | Responses API |
|
|
286
|
+
| Anthropic | ✅ | - | Yes | Messages API |
|
|
287
|
+
| Google Gemini | ✅ | - | Yes | GenerateContent API |
|
|
288
|
+
| Compat | ✅ | - | Optional | Universal fallback |
|
|
289
|
+
|
|
290
|
+
- **toGenAI** = Can translate *from* this provider to GenAI (source)
|
|
291
|
+
- **fromGenAI** = Can translate *to* this provider from GenAI (target)
|
|
292
|
+
- **Separated System** = Provider separates system instructions from messages (use the `system` option if needed)
|
|
118
293
|
|
|
119
294
|
### Universal Compatibility
|
|
120
295
|
|
|
@@ -127,29 +302,31 @@ The **Compat** provider is a universal fallback that handles messages from *any*
|
|
|
127
302
|
|
|
128
303
|
```typescript
|
|
129
304
|
// Works with any provider - no need to specify the source
|
|
130
|
-
const
|
|
305
|
+
const messages = [
|
|
131
306
|
{ role: "user", content: "Hello" },
|
|
132
|
-
{ role: "assistant",
|
|
307
|
+
{ role: "assistant", toolCalls: [{ id: "1", function: { name: "search", arguments: "{}" } }] },
|
|
133
308
|
];
|
|
134
309
|
|
|
135
|
-
const { messages } = translate(
|
|
310
|
+
const { messages: translated } = translate(messages); // Auto-detected and translated
|
|
136
311
|
```
|
|
137
312
|
|
|
138
313
|
More providers will be added. See [AGENTS.md](./AGENTS.md) for contribution guidelines.
|
|
139
314
|
|
|
140
315
|
## GenAI Format
|
|
141
316
|
|
|
142
|
-
GenAI is the intermediate format used for translation. It provides a unified representation of LLM messages:
|
|
317
|
+
GenAI is the intermediate format used for translation, inspired by the [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/). It provides a unified representation of LLM messages across all providers:
|
|
143
318
|
|
|
144
319
|
```typescript
|
|
145
|
-
import type { GenAIMessage,
|
|
320
|
+
import type { GenAIMessage, GenAISystem } from "rosetta-ai";
|
|
146
321
|
|
|
147
322
|
const message: GenAIMessage = {
|
|
148
|
-
role: "user",
|
|
149
|
-
parts: [
|
|
150
|
-
{ type: "text", content: "
|
|
151
|
-
{ type: "
|
|
323
|
+
role: "user", // "user" | "assistant" | "system" | "tool" | string
|
|
324
|
+
parts: [ // Array of content parts
|
|
325
|
+
{ type: "text", content: "What's in this image?" },
|
|
326
|
+
{ type: "uri", uri: "https://example.com/cat.jpg", modality: "image" },
|
|
152
327
|
],
|
|
328
|
+
name: "Alice", // Optional: participant name
|
|
329
|
+
finish_reason: "stop", // Optional: why the model stopped
|
|
153
330
|
};
|
|
154
331
|
|
|
155
332
|
const system: GenAISystem = [
|
|
@@ -159,32 +336,90 @@ const system: GenAISystem = [
|
|
|
159
336
|
|
|
160
337
|
### Part Types
|
|
161
338
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
339
|
+
| Type | Description | Key Fields |
|
|
340
|
+
|------|-------------|------------|
|
|
341
|
+
| `text` | Plain text content | `content` |
|
|
342
|
+
| `blob` | Binary data (base64) | `content`, `mime_type`, `modality` |
|
|
343
|
+
| `file` | File reference by ID | `file_id`, `modality` |
|
|
344
|
+
| `uri` | URL reference | `uri`, `modality` |
|
|
345
|
+
| `reasoning` | Model thinking/reasoning | `content` |
|
|
346
|
+
| `tool_call` | Tool/function call request | `call_id`, `name`, `arguments` |
|
|
347
|
+
| `tool_call_response` | Tool/function result | `call_id`, `content` |
|
|
348
|
+
| `generic` | Custom/extensible type | `content`, any additional fields |
|
|
170
349
|
|
|
171
350
|
### Provider Metadata
|
|
172
351
|
|
|
173
|
-
All GenAI entities support `_provider_metadata` to preserve
|
|
352
|
+
All GenAI entities support `_provider_metadata` to preserve data during translation. The metadata has two types of fields:
|
|
353
|
+
|
|
354
|
+
1. **Root-level shared fields** (camelCase): Cross-provider semantic data accessible to any target provider
|
|
355
|
+
2. **Provider-specific slots** (snake_case): Data for same-provider round-trips only
|
|
174
356
|
|
|
175
357
|
```typescript
|
|
176
358
|
const message: GenAIMessage = {
|
|
177
|
-
role: "
|
|
178
|
-
parts: [{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
359
|
+
role: "tool",
|
|
360
|
+
parts: [{
|
|
361
|
+
type: "tool_call_response",
|
|
362
|
+
id: "call_123",
|
|
363
|
+
response: "Error occurred",
|
|
364
|
+
_provider_metadata: {
|
|
365
|
+
// Root-level shared fields - any target provider can read these
|
|
366
|
+
toolName: "get_weather", // Tool name (GenAI schema doesn't include it)
|
|
367
|
+
isError: true, // Error indicator
|
|
368
|
+
|
|
369
|
+
// Provider-specific slot - only for same-provider round-trips
|
|
370
|
+
openai_completions: { annotations: [...] },
|
|
371
|
+
},
|
|
372
|
+
}],
|
|
182
373
|
};
|
|
183
374
|
```
|
|
184
375
|
|
|
376
|
+
**Shared fields**: `toolName`, `isError`, `isRefusal`, `originalType`
|
|
377
|
+
|
|
378
|
+
**Provider slots**: `openai_completions`, `openai_responses`, `anthropic`, `google`, `vercel_ai`, `promptl`, `compat`
|
|
379
|
+
|
|
380
|
+
This design enables lossless cross-provider translation while keeping providers isolated from each other.
|
|
381
|
+
|
|
382
|
+
## TypeScript Support
|
|
383
|
+
|
|
384
|
+
All types are exported for type-safe usage:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import {
|
|
388
|
+
// Core types
|
|
389
|
+
type GenAIMessage,
|
|
390
|
+
type GenAIPart,
|
|
391
|
+
type GenAISystem,
|
|
392
|
+
|
|
393
|
+
// API types
|
|
394
|
+
type TranslateOptions,
|
|
395
|
+
type TranslateResult,
|
|
396
|
+
|
|
397
|
+
// Provider types
|
|
398
|
+
Provider,
|
|
399
|
+
type ProviderMessage,
|
|
400
|
+
type ProviderSystem,
|
|
401
|
+
} from "rosetta-ai";
|
|
402
|
+
|
|
403
|
+
// Type-safe translation
|
|
404
|
+
const result: TranslateResult<Provider.GenAI> = translate(messages);
|
|
405
|
+
|
|
406
|
+
// Access provider-specific message types
|
|
407
|
+
type OpenAIMsg = ProviderMessage<Provider.OpenAICompletions>;
|
|
408
|
+
```
|
|
409
|
+
|
|
185
410
|
## Examples
|
|
186
411
|
|
|
187
|
-
|
|
412
|
+
The [examples](./examples) folder contains E2E tests demonstrating real-world usage with actual provider SDKs:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
cd examples
|
|
416
|
+
pnpm install
|
|
417
|
+
pnpm test # Runs tests (imports directly from src, no build needed)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Tests include:
|
|
421
|
+
- **Real API calls** (when API keys are set) - validates against actual provider responses
|
|
422
|
+
- **Hardcoded messages** - runs without API keys for fast iteration
|
|
188
423
|
|
|
189
424
|
## Development
|
|
190
425
|
|