rosetta-ai 1.6.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -509,12 +509,11 @@ const AnthropicSpecification = {
509
509
  messageSchema: AnthropicMessageSchema,
510
510
  systemSchema: AnthropicSystemSchema,
511
511
  toGenAI({ messages, system, direction }) {
512
- // Handle string input
512
+ // Handle string input - wrap in Anthropic format then fall through
513
+ // (avoids skipping system instruction handling below)
513
514
  if (typeof messages === "string") {
514
515
  const role = direction === "input" ? "user" : "assistant";
515
- return {
516
- messages: [{ role, parts: [{ type: "text", content: messages }] }],
517
- };
516
+ messages = [{ role, content: messages }];
518
517
  }
519
518
  // Validate with schema
520
519
  const parsedMessages = AnthropicMessageSchema.array().parse(messages);
@@ -947,12 +946,11 @@ const CompatSpecification = {
947
946
  messageSchema: CompatMessageSchema,
948
947
  systemSchema: CompatSystemSchema,
949
948
  toGenAI({ messages, system, direction }) {
950
- // Handle string input
949
+ // Handle string input - wrap in compat format then fall through
950
+ // (avoids skipping system instruction handling below)
951
951
  if (typeof messages === "string") {
952
952
  const role = direction === "input" ? "user" : "assistant";
953
- return {
954
- messages: [{ role, parts: [{ type: "text", content: messages }] }],
955
- };
953
+ messages = [{ role, content: messages }];
956
954
  }
957
955
  // Validate with permissive schema
958
956
  const parsedMessages = CompatMessageSchema.array().parse(messages);
@@ -1090,7 +1088,7 @@ function convertMessage(message, direction) {
1090
1088
  }
1091
1089
  // Handle tool response message (role: tool with tool_call_id)
1092
1090
  if ((role === "tool" || role === "function") && parts.length > 0) {
1093
- const toolCallId = getProperty(normalized, "toolCallId") ?? getProperty(normalized, "toolUseId") ?? null;
1091
+ const toolCallId = getProperty(normalized, "toolCallId") ?? getProperty(normalized, "toolUseId") ?? getProperty(normalized, "toolId") ?? null;
1094
1092
  const toolName = getProperty(normalized, "name") ?? getProperty(normalized, "toolName");
1095
1093
  // Check if we already have tool_call_response parts from content
1096
1094
  const hasToolResponse = parts.some((p) => p.type === "tool_call_response");
@@ -1202,6 +1200,11 @@ function convertTypedPart(part, type) {
1202
1200
  if (url) {
1203
1201
  return [convertImageUrl(url)];
1204
1202
  }
1203
+ // Handle image_url as a direct string (data URI or URL)
1204
+ const imageUrlStr = getStringProperty(part, "imageUrl");
1205
+ if (imageUrlStr) {
1206
+ return [convertImageUrl(imageUrlStr)];
1207
+ }
1205
1208
  return [];
1206
1209
  }
1207
1210
  case "image": {
@@ -1215,6 +1218,21 @@ function convertTypedPart(part, type) {
1215
1218
  if (image) {
1216
1219
  return [convertImageUrl(image)];
1217
1220
  }
1221
+ // Fallback: check url or data fields
1222
+ const imageUri = getStringProperty(part, "url") ?? getStringProperty(part, "uri");
1223
+ if (imageUri) {
1224
+ return [convertImageUrl(imageUri)];
1225
+ }
1226
+ const imageData = getStringProperty(part, "data");
1227
+ const imageMime = getStringProperty(part, "mediaType") ?? getStringProperty(part, "mimeType");
1228
+ if (imageData) {
1229
+ if (imageData.startsWith("data:")) {
1230
+ return [convertImageUrl(imageData)];
1231
+ }
1232
+ return [
1233
+ { type: "blob", modality: "image", content: imageData, ...(imageMime ? { mime_type: imageMime } : {}) },
1234
+ ];
1235
+ }
1218
1236
  return [];
1219
1237
  }
1220
1238
  case "input_audio":
@@ -1248,6 +1266,25 @@ function convertTypedPart(part, type) {
1248
1266
  return [{ type: "blob", modality, content: fileData, ...(mimeType ? { mime_type: mimeType } : {}) }];
1249
1267
  }
1250
1268
  }
1269
+ // Handle file field as string (data URI, URL, or filename)
1270
+ const fileStr = getStringProperty(part, "file");
1271
+ if (fileStr) {
1272
+ const mimeType = getStringProperty(part, "mediaType") ?? getStringProperty(part, "mimeType");
1273
+ const modality = inferModality(mimeType);
1274
+ if (fileStr.startsWith("data:")) {
1275
+ const match = fileStr.match(/^data:([^;,]+)?(?:;base64)?,(.*)$/);
1276
+ if (match) {
1277
+ const parsedMime = match[1] || mimeType || "application/octet-stream";
1278
+ const data = match[2] || "";
1279
+ return [{ type: "blob", modality: inferModality(parsedMime), mime_type: parsedMime, content: data }];
1280
+ }
1281
+ }
1282
+ if (isUrlString(fileStr)) {
1283
+ return [{ type: "uri", modality, uri: fileStr, ...(mimeType ? { mime_type: mimeType } : {}) }];
1284
+ }
1285
+ // Treat as file ID
1286
+ return [{ type: "file", modality, file_id: fileStr, ...(mimeType ? { mime_type: mimeType } : {}) }];
1287
+ }
1251
1288
  if (source) {
1252
1289
  return [convertAnthropicDocumentSource(source)];
1253
1290
  }
@@ -1888,14 +1925,14 @@ function groupPartsByMessageIndex(parts) {
1888
1925
  /** Inline blob data (images, audio, etc.) */
1889
1926
  const GoogleBlobSchema = zod.z
1890
1927
  .object({
1891
- data: zod.z.string().optional(),
1892
- mimeType: zod.z.string().optional(),
1928
+ data: zod.z.string(),
1929
+ mimeType: zod.z.string(),
1893
1930
  })
1894
1931
  .passthrough();
1895
1932
  /** File reference by URI */
1896
1933
  const GoogleFileDataSchema = zod.z
1897
1934
  .object({
1898
- fileUri: zod.z.string().optional(),
1935
+ fileUri: zod.z.string(),
1899
1936
  mimeType: zod.z.string().optional(),
1900
1937
  })
1901
1938
  .passthrough();
@@ -1903,29 +1940,29 @@ const GoogleFileDataSchema = zod.z
1903
1940
  const GoogleFunctionCallSchema = zod.z
1904
1941
  .object({
1905
1942
  id: zod.z.string().optional(),
1906
- name: zod.z.string().optional(),
1907
- args: zod.z.unknown().optional(),
1943
+ name: zod.z.string(),
1944
+ args: zod.z.object({}).passthrough().optional(),
1908
1945
  })
1909
1946
  .passthrough();
1910
1947
  /** Function response provided to the model */
1911
1948
  const GoogleFunctionResponseSchema = zod.z
1912
1949
  .object({
1913
1950
  id: zod.z.string().optional(),
1914
- name: zod.z.string().optional(),
1915
- response: zod.z.unknown().optional(),
1951
+ name: zod.z.string(),
1952
+ response: zod.z.object({}).passthrough(),
1916
1953
  })
1917
1954
  .passthrough();
1918
1955
  /** Executable code generated by the model */
1919
1956
  const GoogleExecutableCodeSchema = zod.z
1920
1957
  .object({
1921
- code: zod.z.string().optional(),
1922
- language: zod.z.string().optional(),
1958
+ code: zod.z.string(),
1959
+ language: zod.z.string(),
1923
1960
  })
1924
1961
  .passthrough();
1925
1962
  /** Code execution result */
1926
1963
  const GoogleCodeExecutionResultSchema = zod.z
1927
1964
  .object({
1928
- outcome: zod.z.string().optional(),
1965
+ outcome: zod.z.string(),
1929
1966
  output: zod.z.string().optional(),
1930
1967
  })
1931
1968
  .passthrough();
@@ -1969,13 +2006,13 @@ const GooglePartSchema = zod.z
1969
2006
  /**
1970
2007
  * Content (message) schema.
1971
2008
  *
1972
- * The role is optional and can be 'user' or 'model'.
1973
- * Parts is an array of Part objects.
2009
+ * Parts is a required array of Part objects.
2010
+ * Role is optional and can be 'user' or 'model'.
1974
2011
  */
1975
2012
  const GoogleContentSchema = zod.z
1976
2013
  .object({
1977
2014
  role: zod.z.string().optional(),
1978
- parts: zod.z.array(GooglePartSchema).optional(),
2015
+ parts: zod.z.array(GooglePartSchema),
1979
2016
  })
1980
2017
  .passthrough();
1981
2018
  /** System instruction - uses the same Content/Part structure */
@@ -2007,12 +2044,11 @@ const GoogleSpecification = {
2007
2044
  messageSchema: GoogleContentSchema,
2008
2045
  systemSchema: GoogleSystemSchema,
2009
2046
  toGenAI({ messages, system, direction }) {
2010
- // Handle string input
2047
+ // Handle string input - wrap in Google format (role: "user"/"model", parts with text)
2048
+ // then fall through to the normal conversion pipeline
2011
2049
  if (typeof messages === "string") {
2012
2050
  const role = direction === "input" ? "user" : "model";
2013
- return {
2014
- messages: [{ role, parts: [{ type: "text", content: messages }] }],
2015
- };
2051
+ messages = [{ role, parts: [{ text: messages }] }];
2016
2052
  }
2017
2053
  // Validate with schema
2018
2054
  const parsedMessages = GoogleContentSchema.array().parse(messages);
@@ -2067,7 +2103,7 @@ function googleContentToGenAI(content, direction) {
2067
2103
  const existingMetadata = readMetadata(content);
2068
2104
  const parts = [];
2069
2105
  // Convert each part
2070
- for (const part of content.parts ?? []) {
2106
+ for (const part of content.parts) {
2071
2107
  parts.push(...convertPart(part));
2072
2108
  }
2073
2109
  // Map role: 'model' -> 'assistant', otherwise keep as-is
@@ -2138,8 +2174,8 @@ function convertPart(part) {
2138
2174
  {
2139
2175
  type: "blob",
2140
2176
  modality,
2141
- mime_type: blob.mimeType ?? null,
2142
- content: blob.data ?? "",
2177
+ mime_type: blob.mimeType,
2178
+ content: blob.data,
2143
2179
  ...(metadata ? { _provider_metadata: metadata } : {}),
2144
2180
  },
2145
2181
  ];
@@ -2155,7 +2191,7 @@ function convertPart(part) {
2155
2191
  type: "uri",
2156
2192
  modality,
2157
2193
  mime_type: file.mimeType ?? null,
2158
- uri: file.fileUri ?? "",
2194
+ uri: file.fileUri,
2159
2195
  ...(metadata ? { _provider_metadata: metadata } : {}),
2160
2196
  },
2161
2197
  ];
@@ -2169,7 +2205,7 @@ function convertPart(part) {
2169
2205
  {
2170
2206
  type: "tool_call",
2171
2207
  id: fc.id ?? null,
2172
- name: fc.name ?? "",
2208
+ name: fc.name,
2173
2209
  arguments: fc.args,
2174
2210
  ...(metadata ? { _provider_metadata: metadata } : {}),
2175
2211
  },
@@ -2179,8 +2215,12 @@ function convertPart(part) {
2179
2215
  if (part.functionResponse !== undefined) {
2180
2216
  const fr = part.functionResponse;
2181
2217
  const frExtra = extractExtraFields(fr, ["id", "name", "response"]);
2182
- // Store toolName in known fields
2183
- const metadata = storeMetadata(existingMetadata, { ...extraFields, ...frExtra }, fr.name ? { toolName: fr.name } : {});
2218
+ // Detect error: per Gemini docs, a failed function call has an "error" key in the response
2219
+ const isError = "error" in fr.response;
2220
+ const metadata = storeMetadata(existingMetadata, { ...extraFields, ...frExtra }, {
2221
+ toolName: fr.name,
2222
+ ...(isError ? { isError: true } : {}),
2223
+ });
2184
2224
  return [
2185
2225
  {
2186
2226
  type: "tool_call_response",
@@ -2198,7 +2238,7 @@ function convertPart(part) {
2198
2238
  return [
2199
2239
  {
2200
2240
  type: "executable_code",
2201
- code: code.code ?? "",
2241
+ code: code.code,
2202
2242
  language: code.language,
2203
2243
  ...(metadata ? { _provider_metadata: metadata } : {}),
2204
2244
  },
@@ -2410,12 +2450,10 @@ const OpenAICompletionsSpecification = {
2410
2450
  name: "OpenAI Completions",
2411
2451
  messageSchema: OpenAICompletionsMessageSchema,
2412
2452
  toGenAI({ messages, direction }) {
2413
- // Handle string input
2453
+ // Handle string input - wrap in OpenAI Completions format then fall through
2414
2454
  if (typeof messages === "string") {
2415
2455
  const role = direction === "input" ? "user" : "assistant";
2416
- return {
2417
- messages: [{ role, parts: [{ type: "text", content: messages }] }],
2418
- };
2456
+ messages = [{ role, content: messages }];
2419
2457
  }
2420
2458
  // Validate with schema
2421
2459
  const parsedMessages = OpenAICompletionsMessageSchema.array().parse(messages);
@@ -2853,12 +2891,10 @@ const OpenAIResponsesSpecification = {
2853
2891
  name: "OpenAI Responses",
2854
2892
  messageSchema: OpenAIResponsesItemSchema,
2855
2893
  toGenAI({ messages, direction }) {
2856
- // Handle string input
2894
+ // Handle string input - wrap in OpenAI Responses format then fall through
2857
2895
  if (typeof messages === "string") {
2858
2896
  const role = direction === "input" ? "user" : "assistant";
2859
- return {
2860
- messages: [{ role, parts: [{ type: "text", content: messages }] }],
2861
- };
2897
+ messages = [{ role, content: messages }];
2862
2898
  }
2863
2899
  // Validate with schema
2864
2900
  const parsedItems = OpenAIResponsesItemSchema.array().parse(messages);