@ai-sdk/otel 1.0.0-beta.3 → 1.0.0-beta.30
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/CHANGELOG.md +192 -0
- package/dist/index.d.mts +50 -5
- package/dist/index.d.ts +50 -5
- package/dist/index.js +1105 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1111 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/gen-ai-format-messages.ts +573 -0
- package/src/gen-ai-open-telemetry-integration.ts +928 -0
- package/src/index.ts +1 -0
- package/src/open-telemetry-integration.ts +14 -14
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LanguageModelV4CustomPart,
|
|
3
|
+
LanguageModelV4FilePart,
|
|
4
|
+
LanguageModelV4Message,
|
|
5
|
+
LanguageModelV4Prompt,
|
|
6
|
+
LanguageModelV4ReasoningFilePart,
|
|
7
|
+
LanguageModelV4ReasoningPart,
|
|
8
|
+
LanguageModelV4TextPart,
|
|
9
|
+
LanguageModelV4ToolApprovalResponsePart,
|
|
10
|
+
LanguageModelV4ToolCallPart,
|
|
11
|
+
LanguageModelV4ToolResultPart,
|
|
12
|
+
} from '@ai-sdk/provider';
|
|
13
|
+
import type { ModelMessage } from '@ai-sdk/provider-utils';
|
|
14
|
+
import { convertDataContentToBase64String } from 'ai';
|
|
15
|
+
|
|
16
|
+
type LanguageModelV4ContentPart =
|
|
17
|
+
| LanguageModelV4TextPart
|
|
18
|
+
| LanguageModelV4FilePart
|
|
19
|
+
| LanguageModelV4CustomPart
|
|
20
|
+
| LanguageModelV4ReasoningPart
|
|
21
|
+
| LanguageModelV4ReasoningFilePart
|
|
22
|
+
| LanguageModelV4ToolCallPart
|
|
23
|
+
| LanguageModelV4ToolResultPart
|
|
24
|
+
| LanguageModelV4ToolApprovalResponsePart;
|
|
25
|
+
|
|
26
|
+
type SemConvPart =
|
|
27
|
+
| { type: 'text'; content: string }
|
|
28
|
+
| { type: 'reasoning'; content: string }
|
|
29
|
+
| {
|
|
30
|
+
type: 'tool_call';
|
|
31
|
+
id: string | null;
|
|
32
|
+
name: string;
|
|
33
|
+
arguments?: unknown;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
type: 'tool_call_response';
|
|
37
|
+
id: string | null;
|
|
38
|
+
response: unknown;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'blob';
|
|
42
|
+
modality: string;
|
|
43
|
+
mime_type: string | null;
|
|
44
|
+
content: string;
|
|
45
|
+
}
|
|
46
|
+
| { type: string; [key: string]: unknown };
|
|
47
|
+
|
|
48
|
+
interface SemConvInputMessage {
|
|
49
|
+
role: string;
|
|
50
|
+
parts: SemConvPart[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface SemConvOutputMessage {
|
|
54
|
+
role: string;
|
|
55
|
+
parts: SemConvPart[];
|
|
56
|
+
finish_reason: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SemConvSystemInstruction {
|
|
60
|
+
type: string;
|
|
61
|
+
content?: string;
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Maps an AI SDK provider string (e.g. "anthropic.messages", "openai.chat")
|
|
67
|
+
* to a well-known gen_ai.provider.name value per the OTel GenAI SemConv.
|
|
68
|
+
*
|
|
69
|
+
* Provider strings come in formats like:
|
|
70
|
+
* "openai.chat", "google.generative-ai", "google.vertex.chat",
|
|
71
|
+
* "google-vertex", "amazon-bedrock.chat", "azure.chat"
|
|
72
|
+
*
|
|
73
|
+
* We match against longest-prefix-first to handle multi-segment prefixes
|
|
74
|
+
* like "google.vertex" and "google.generative-ai" before the single-segment "google".
|
|
75
|
+
*/
|
|
76
|
+
export function mapProviderName(provider: string): string {
|
|
77
|
+
const lower = provider.toLowerCase();
|
|
78
|
+
|
|
79
|
+
const wellKnownPrefixes: Array<[string, string]> = [
|
|
80
|
+
['google.vertex', 'gcp.vertex_ai'],
|
|
81
|
+
['google.generative-ai', 'gcp.gemini'],
|
|
82
|
+
['google-vertex', 'gcp.vertex_ai'],
|
|
83
|
+
['amazon-bedrock', 'aws.bedrock'],
|
|
84
|
+
['azure-openai', 'azure.ai.openai'],
|
|
85
|
+
['anthropic', 'anthropic'],
|
|
86
|
+
['openai', 'openai'],
|
|
87
|
+
['azure', 'azure.ai.inference'],
|
|
88
|
+
['google', 'gcp.gemini'],
|
|
89
|
+
['mistral', 'mistral_ai'],
|
|
90
|
+
['cohere', 'cohere'],
|
|
91
|
+
['bedrock', 'aws.bedrock'],
|
|
92
|
+
['groq', 'groq'],
|
|
93
|
+
['deepseek', 'deepseek'],
|
|
94
|
+
['perplexity', 'perplexity'],
|
|
95
|
+
['xai', 'x_ai'],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const [prefix, mapped] of wellKnownPrefixes) {
|
|
99
|
+
if (
|
|
100
|
+
lower === prefix ||
|
|
101
|
+
lower.startsWith(prefix + '.') ||
|
|
102
|
+
lower.startsWith(prefix + '-')
|
|
103
|
+
) {
|
|
104
|
+
return mapped;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return provider;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Maps an AI SDK operationId to a gen_ai.operation.name value.
|
|
113
|
+
*/
|
|
114
|
+
export function mapOperationName(operationId: string): string {
|
|
115
|
+
const mapping: Record<string, string> = {
|
|
116
|
+
'ai.generateText': 'invoke_agent',
|
|
117
|
+
'ai.streamText': 'invoke_agent',
|
|
118
|
+
'ai.generateObject': 'invoke_agent',
|
|
119
|
+
'ai.streamObject': 'invoke_agent',
|
|
120
|
+
'ai.embed': 'embeddings',
|
|
121
|
+
'ai.embedMany': 'embeddings',
|
|
122
|
+
'ai.rerank': 'rerank',
|
|
123
|
+
};
|
|
124
|
+
return mapping[operationId] ?? operationId;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Converts a system value to the gen_ai.system_instructions SemConv format.
|
|
129
|
+
* Accepts a plain string, a single SystemModelMessage, or an array of them.
|
|
130
|
+
* Schema: array of parts, each with at least { type, content }.
|
|
131
|
+
*/
|
|
132
|
+
export function formatSystemInstructions(
|
|
133
|
+
system:
|
|
134
|
+
| string
|
|
135
|
+
| { role: 'system'; content: string }
|
|
136
|
+
| Array<{ role: 'system'; content: string }>,
|
|
137
|
+
): SemConvSystemInstruction[] {
|
|
138
|
+
if (typeof system === 'string') {
|
|
139
|
+
return [{ type: 'text', content: system }];
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(system)) {
|
|
142
|
+
return system.map(msg => ({ type: 'text', content: msg.content }));
|
|
143
|
+
}
|
|
144
|
+
return [{ type: 'text', content: system.content }];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function convertMessagePartToSemConv(
|
|
148
|
+
part: LanguageModelV4ContentPart,
|
|
149
|
+
): SemConvPart {
|
|
150
|
+
switch (part.type) {
|
|
151
|
+
case 'text':
|
|
152
|
+
return { type: 'text', content: part.text };
|
|
153
|
+
|
|
154
|
+
case 'reasoning':
|
|
155
|
+
return { type: 'reasoning', content: part.text };
|
|
156
|
+
|
|
157
|
+
case 'tool-call':
|
|
158
|
+
return {
|
|
159
|
+
type: 'tool_call',
|
|
160
|
+
id: part.toolCallId ?? null,
|
|
161
|
+
name: part.toolName,
|
|
162
|
+
arguments: part.input,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
case 'tool-result': {
|
|
166
|
+
const output = part.output;
|
|
167
|
+
let response: unknown;
|
|
168
|
+
if (output) {
|
|
169
|
+
if (output.type === 'text' || output.type === 'error-text') {
|
|
170
|
+
response = output.value;
|
|
171
|
+
} else if (output.type === 'json' || output.type === 'error-json') {
|
|
172
|
+
response = output.value;
|
|
173
|
+
} else if (output.type === 'execution-denied') {
|
|
174
|
+
response = { denied: true, reason: output.reason };
|
|
175
|
+
} else {
|
|
176
|
+
response = output;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
type: 'tool_call_response',
|
|
181
|
+
id: part.toolCallId ?? null,
|
|
182
|
+
response,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case 'file': {
|
|
187
|
+
const data = part.data;
|
|
188
|
+
let content: string;
|
|
189
|
+
if (data instanceof Uint8Array) {
|
|
190
|
+
content = convertDataContentToBase64String(data);
|
|
191
|
+
} else if (typeof data === 'string') {
|
|
192
|
+
if (data.startsWith('http://') || data.startsWith('https://')) {
|
|
193
|
+
return {
|
|
194
|
+
type: 'uri',
|
|
195
|
+
modality: getModality(part.mediaType),
|
|
196
|
+
mime_type: part.mediaType ?? null,
|
|
197
|
+
uri: data,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
content = data;
|
|
201
|
+
} else {
|
|
202
|
+
content = String(data);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
type: 'blob',
|
|
206
|
+
modality: getModality(part.mediaType),
|
|
207
|
+
mime_type: part.mediaType ?? null,
|
|
208
|
+
content,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case 'tool-approval-response':
|
|
213
|
+
return {
|
|
214
|
+
type: 'tool_approval_response',
|
|
215
|
+
approval_id: part.approvalId,
|
|
216
|
+
approved: part.approved,
|
|
217
|
+
reason: part.reason,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
case 'custom':
|
|
221
|
+
return { type: 'custom', kind: part.kind };
|
|
222
|
+
|
|
223
|
+
case 'reasoning-file':
|
|
224
|
+
return { type: String(part.type) };
|
|
225
|
+
|
|
226
|
+
default: {
|
|
227
|
+
const _exhaustive: never = part;
|
|
228
|
+
return { type: String((_exhaustive as { type: string }).type) };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function getModality(mediaType: string | undefined): string {
|
|
234
|
+
if (!mediaType) return 'image';
|
|
235
|
+
if (mediaType.startsWith('image/')) return 'image';
|
|
236
|
+
if (mediaType.startsWith('video/')) return 'video';
|
|
237
|
+
if (mediaType.startsWith('audio/')) return 'audio';
|
|
238
|
+
return 'image';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Converts a LanguageModelV4Prompt to the gen_ai.input.messages SemConv format.
|
|
243
|
+
* System messages are excluded (they go into gen_ai.system_instructions).
|
|
244
|
+
*/
|
|
245
|
+
export function formatInputMessages(
|
|
246
|
+
prompt: LanguageModelV4Prompt,
|
|
247
|
+
): SemConvInputMessage[] {
|
|
248
|
+
return prompt
|
|
249
|
+
.filter(msg => msg.role !== 'system')
|
|
250
|
+
.map((message: LanguageModelV4Message) => {
|
|
251
|
+
if (message.role === 'system') {
|
|
252
|
+
return {
|
|
253
|
+
role: 'system',
|
|
254
|
+
parts: [{ type: 'text', content: message.content }],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const parts = message.content.map(convertMessagePartToSemConv);
|
|
259
|
+
return { role: message.role, parts };
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Converts user-facing ModelMessage[] (and optional prompt string) to the
|
|
265
|
+
* gen_ai.input.messages SemConv format. System messages are excluded
|
|
266
|
+
* (they belong in gen_ai.system_instructions).
|
|
267
|
+
*/
|
|
268
|
+
export function formatModelMessages({
|
|
269
|
+
prompt,
|
|
270
|
+
messages,
|
|
271
|
+
}: {
|
|
272
|
+
prompt: string | Array<ModelMessage> | undefined;
|
|
273
|
+
messages: Array<ModelMessage> | undefined;
|
|
274
|
+
}): SemConvInputMessage[] {
|
|
275
|
+
const result: SemConvInputMessage[] = [];
|
|
276
|
+
|
|
277
|
+
if (typeof prompt === 'string') {
|
|
278
|
+
result.push({
|
|
279
|
+
role: 'user',
|
|
280
|
+
parts: [{ type: 'text', content: prompt }],
|
|
281
|
+
});
|
|
282
|
+
} else if (Array.isArray(prompt)) {
|
|
283
|
+
for (const msg of prompt) {
|
|
284
|
+
const converted = convertModelMessageToSemConv(msg);
|
|
285
|
+
if (converted) result.push(converted);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (messages) {
|
|
290
|
+
for (const msg of messages) {
|
|
291
|
+
const converted = convertModelMessageToSemConv(msg);
|
|
292
|
+
if (converted) result.push(converted);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function convertModelMessageToSemConv(
|
|
300
|
+
msg: ModelMessage,
|
|
301
|
+
): SemConvInputMessage | undefined {
|
|
302
|
+
if (msg.role === 'system') return undefined;
|
|
303
|
+
|
|
304
|
+
if (msg.role === 'user') {
|
|
305
|
+
if (typeof msg.content === 'string') {
|
|
306
|
+
return {
|
|
307
|
+
role: 'user',
|
|
308
|
+
parts: [{ type: 'text', content: msg.content }],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const parts: SemConvPart[] = msg.content.map(part => {
|
|
312
|
+
switch (part.type) {
|
|
313
|
+
case 'text':
|
|
314
|
+
return { type: 'text' as const, content: part.text };
|
|
315
|
+
case 'image': {
|
|
316
|
+
const data = part.image;
|
|
317
|
+
if (data instanceof URL) {
|
|
318
|
+
return {
|
|
319
|
+
type: 'uri' as const,
|
|
320
|
+
modality: 'image',
|
|
321
|
+
mime_type: part.mediaType ?? null,
|
|
322
|
+
uri: data.toString(),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (typeof data === 'string') {
|
|
326
|
+
if (data.startsWith('http://') || data.startsWith('https://')) {
|
|
327
|
+
return {
|
|
328
|
+
type: 'uri' as const,
|
|
329
|
+
modality: 'image',
|
|
330
|
+
mime_type: part.mediaType ?? null,
|
|
331
|
+
uri: data,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
type: 'blob' as const,
|
|
336
|
+
modality: 'image',
|
|
337
|
+
mime_type: part.mediaType ?? null,
|
|
338
|
+
content: data,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
type: 'blob' as const,
|
|
343
|
+
modality: 'image',
|
|
344
|
+
mime_type: part.mediaType ?? null,
|
|
345
|
+
content: convertDataContentToBase64String(data as Uint8Array),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
case 'file': {
|
|
349
|
+
const data = part.data;
|
|
350
|
+
if (data instanceof URL) {
|
|
351
|
+
return {
|
|
352
|
+
type: 'uri' as const,
|
|
353
|
+
modality: getModality(part.mediaType),
|
|
354
|
+
mime_type: part.mediaType ?? null,
|
|
355
|
+
uri: data.toString(),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (typeof data === 'string') {
|
|
359
|
+
if (data.startsWith('http://') || data.startsWith('https://')) {
|
|
360
|
+
return {
|
|
361
|
+
type: 'uri' as const,
|
|
362
|
+
modality: getModality(part.mediaType),
|
|
363
|
+
mime_type: part.mediaType ?? null,
|
|
364
|
+
uri: data,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
type: 'blob' as const,
|
|
369
|
+
modality: getModality(part.mediaType),
|
|
370
|
+
mime_type: part.mediaType ?? null,
|
|
371
|
+
content: data,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
type: 'blob' as const,
|
|
376
|
+
modality: getModality(part.mediaType),
|
|
377
|
+
mime_type: part.mediaType ?? null,
|
|
378
|
+
content: convertDataContentToBase64String(data as Uint8Array),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
default:
|
|
382
|
+
return { type: String((part as { type: string }).type) };
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
return { role: 'user', parts };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (msg.role === 'assistant') {
|
|
389
|
+
if (typeof msg.content === 'string') {
|
|
390
|
+
return {
|
|
391
|
+
role: 'assistant',
|
|
392
|
+
parts: [{ type: 'text', content: msg.content }],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const parts: SemConvPart[] = msg.content.map(part => {
|
|
396
|
+
switch (part.type) {
|
|
397
|
+
case 'text':
|
|
398
|
+
return { type: 'text' as const, content: part.text };
|
|
399
|
+
case 'reasoning':
|
|
400
|
+
return { type: 'reasoning' as const, content: part.text };
|
|
401
|
+
case 'tool-call':
|
|
402
|
+
return {
|
|
403
|
+
type: 'tool_call' as const,
|
|
404
|
+
id: part.toolCallId ?? null,
|
|
405
|
+
name: part.toolName,
|
|
406
|
+
arguments: part.input,
|
|
407
|
+
};
|
|
408
|
+
case 'tool-result': {
|
|
409
|
+
const output = part.output;
|
|
410
|
+
let response: unknown;
|
|
411
|
+
if (output) {
|
|
412
|
+
if (output.type === 'text' || output.type === 'error-text') {
|
|
413
|
+
response = output.value;
|
|
414
|
+
} else if (output.type === 'json' || output.type === 'error-json') {
|
|
415
|
+
response = output.value;
|
|
416
|
+
} else if (output.type === 'execution-denied') {
|
|
417
|
+
response = { denied: true, reason: output.reason };
|
|
418
|
+
} else {
|
|
419
|
+
response = output;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
type: 'tool_call_response' as const,
|
|
424
|
+
id: part.toolCallId ?? null,
|
|
425
|
+
response,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
default:
|
|
429
|
+
return { type: String((part as { type: string }).type) };
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return { role: 'assistant', parts };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (msg.role === 'tool') {
|
|
436
|
+
const parts: SemConvPart[] = msg.content.map(part => {
|
|
437
|
+
if (part.type === 'tool-result') {
|
|
438
|
+
const output = part.output;
|
|
439
|
+
let response: unknown;
|
|
440
|
+
if (output) {
|
|
441
|
+
if (output.type === 'text' || output.type === 'error-text') {
|
|
442
|
+
response = output.value;
|
|
443
|
+
} else if (output.type === 'json' || output.type === 'error-json') {
|
|
444
|
+
response = output.value;
|
|
445
|
+
} else if (output.type === 'execution-denied') {
|
|
446
|
+
response = { denied: true, reason: output.reason };
|
|
447
|
+
} else {
|
|
448
|
+
response = output;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
type: 'tool_call_response' as const,
|
|
453
|
+
id: part.toolCallId ?? null,
|
|
454
|
+
response,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return { type: String((part as { type: string }).type) };
|
|
458
|
+
});
|
|
459
|
+
return { role: 'tool', parts };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Extracts the system instruction from a LanguageModelV4Prompt if present.
|
|
467
|
+
*/
|
|
468
|
+
export function extractSystemFromPrompt(
|
|
469
|
+
prompt: LanguageModelV4Prompt,
|
|
470
|
+
): string | undefined {
|
|
471
|
+
const systemMsg = prompt.find(msg => msg.role === 'system');
|
|
472
|
+
if (systemMsg && systemMsg.role === 'system') {
|
|
473
|
+
return systemMsg.content;
|
|
474
|
+
}
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Converts step result data to the gen_ai.output.messages SemConv format.
|
|
480
|
+
*/
|
|
481
|
+
export function formatOutputMessages({
|
|
482
|
+
text,
|
|
483
|
+
reasoning,
|
|
484
|
+
toolCalls,
|
|
485
|
+
files,
|
|
486
|
+
finishReason,
|
|
487
|
+
}: {
|
|
488
|
+
text?: string;
|
|
489
|
+
reasoning?: ReadonlyArray<{ text?: string }>;
|
|
490
|
+
toolCalls?: ReadonlyArray<{
|
|
491
|
+
toolCallId: string;
|
|
492
|
+
toolName: string;
|
|
493
|
+
input: unknown;
|
|
494
|
+
}>;
|
|
495
|
+
files?: ReadonlyArray<{ mediaType: string; base64: string }>;
|
|
496
|
+
finishReason: string;
|
|
497
|
+
}): SemConvOutputMessage[] {
|
|
498
|
+
const parts: SemConvPart[] = [];
|
|
499
|
+
|
|
500
|
+
if (reasoning) {
|
|
501
|
+
for (const r of reasoning) {
|
|
502
|
+
if ('text' in r && r.text) {
|
|
503
|
+
parts.push({ type: 'reasoning', content: r.text });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (text != null && text.length > 0) {
|
|
509
|
+
parts.push({ type: 'text', content: text });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (toolCalls) {
|
|
513
|
+
for (const tc of toolCalls) {
|
|
514
|
+
parts.push({
|
|
515
|
+
type: 'tool_call',
|
|
516
|
+
id: tc.toolCallId,
|
|
517
|
+
name: tc.toolName,
|
|
518
|
+
arguments: tc.input,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (files) {
|
|
524
|
+
for (const file of files) {
|
|
525
|
+
parts.push({
|
|
526
|
+
type: 'blob',
|
|
527
|
+
modality: getModality(file.mediaType),
|
|
528
|
+
mime_type: file.mediaType,
|
|
529
|
+
content: file.base64,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return [
|
|
535
|
+
{
|
|
536
|
+
role: 'assistant',
|
|
537
|
+
parts,
|
|
538
|
+
finish_reason: mapFinishReason(finishReason),
|
|
539
|
+
},
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Converts generateObject result to the gen_ai.output.messages SemConv format.
|
|
545
|
+
*/
|
|
546
|
+
export function formatObjectOutputMessages({
|
|
547
|
+
objectText,
|
|
548
|
+
finishReason,
|
|
549
|
+
}: {
|
|
550
|
+
objectText: string;
|
|
551
|
+
finishReason: string;
|
|
552
|
+
}): SemConvOutputMessage[] {
|
|
553
|
+
return [
|
|
554
|
+
{
|
|
555
|
+
role: 'assistant',
|
|
556
|
+
parts: [{ type: 'text', content: objectText }],
|
|
557
|
+
finish_reason: mapFinishReason(finishReason),
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function mapFinishReason(reason: string): string {
|
|
563
|
+
const mapping: Record<string, string> = {
|
|
564
|
+
stop: 'stop',
|
|
565
|
+
length: 'length',
|
|
566
|
+
'content-filter': 'content_filter',
|
|
567
|
+
'tool-calls': 'tool_call',
|
|
568
|
+
error: 'error',
|
|
569
|
+
other: 'stop',
|
|
570
|
+
unknown: 'stop',
|
|
571
|
+
};
|
|
572
|
+
return mapping[reason] ?? reason;
|
|
573
|
+
}
|