veryfront 0.1.263 → 0.1.265

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.
Files changed (41) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/hosted-child-mirror.d.ts +91 -0
  3. package/esm/src/agent/hosted-child-mirror.d.ts.map +1 -0
  4. package/esm/src/agent/hosted-child-mirror.js +118 -0
  5. package/esm/src/agent/index.d.ts +2 -0
  6. package/esm/src/agent/index.d.ts.map +1 -1
  7. package/esm/src/agent/index.js +2 -0
  8. package/esm/src/chat/hosted-ui-chunk-mapping.d.ts +111 -0
  9. package/esm/src/chat/hosted-ui-chunk-mapping.d.ts.map +1 -0
  10. package/esm/src/chat/hosted-ui-chunk-mapping.js +123 -0
  11. package/esm/src/chat/index.d.ts +1 -0
  12. package/esm/src/chat/index.d.ts.map +1 -1
  13. package/esm/src/chat/index.js +1 -0
  14. package/esm/src/provider/runtime-loader/provider-embedding-responses.d.ts +5 -0
  15. package/esm/src/provider/runtime-loader/provider-embedding-responses.d.ts.map +1 -0
  16. package/esm/src/provider/runtime-loader/provider-embedding-responses.js +50 -0
  17. package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts +9 -0
  18. package/esm/src/provider/runtime-loader/provider-finish-reasons.d.ts.map +1 -0
  19. package/esm/src/provider/runtime-loader/provider-finish-reasons.js +60 -0
  20. package/esm/src/provider/runtime-loader/provider-sse.d.ts +5 -0
  21. package/esm/src/provider/runtime-loader/provider-sse.d.ts.map +1 -0
  22. package/esm/src/provider/runtime-loader/provider-sse.js +23 -0
  23. package/esm/src/provider/runtime-loader/provider-usage.d.ts +19 -0
  24. package/esm/src/provider/runtime-loader/provider-usage.d.ts.map +1 -0
  25. package/esm/src/provider/runtime-loader/provider-usage.js +109 -0
  26. package/esm/src/provider/runtime-loader.d.ts.map +1 -1
  27. package/esm/src/provider/runtime-loader.js +4 -240
  28. package/esm/src/utils/version-constant.d.ts +1 -1
  29. package/esm/src/utils/version-constant.js +1 -1
  30. package/package.json +1 -1
  31. package/src/deno.js +1 -1
  32. package/src/src/agent/hosted-child-mirror.ts +208 -0
  33. package/src/src/agent/index.ts +15 -0
  34. package/src/src/chat/hosted-ui-chunk-mapping.ts +303 -0
  35. package/src/src/chat/index.ts +5 -0
  36. package/src/src/provider/runtime-loader/provider-embedding-responses.ts +61 -0
  37. package/src/src/provider/runtime-loader/provider-finish-reasons.ts +69 -0
  38. package/src/src/provider/runtime-loader/provider-sse.ts +29 -0
  39. package/src/src/provider/runtime-loader/provider-usage.ts +135 -0
  40. package/src/src/provider/runtime-loader.ts +21 -300
  41. package/src/src/utils/version-constant.ts +1 -1
@@ -0,0 +1,208 @@
1
+ import type { ChatMessageMetadata, ChatUiMessageChunk } from "../chat/protocol.js";
2
+ import type { HostedStreamPartForUiChunkMapping } from "../chat/hosted-ui-chunk-mapping.js";
3
+
4
+ export interface HostedChildChunkMirror {
5
+ handleChunk(chunk: ChatUiMessageChunk<ChatMessageMetadata>): Promise<void> | void;
6
+ }
7
+
8
+ export interface HostedChildMirrorState {
9
+ reasoningStarted: boolean;
10
+ textStarted: boolean;
11
+ }
12
+
13
+ type CoreMirroredPartType =
14
+ | "reasoning-delta"
15
+ | "text-delta"
16
+ | "tool-input-start"
17
+ | "tool-input-delta"
18
+ | "tool-call"
19
+ | "tool-result";
20
+
21
+ type MirroredPartType = CoreMirroredPartType | "tool-error" | "error";
22
+
23
+ type DurableMirrorChunkType = ChatUiMessageChunk<ChatMessageMetadata>["type"];
24
+
25
+ const ALREADY_MIRRORED_CHUNK_TYPES_BY_PART_TYPE: Readonly<
26
+ Partial<Record<MirroredPartType, readonly DurableMirrorChunkType[]>>
27
+ > = {
28
+ "reasoning-delta": ["reasoning-delta"],
29
+ "text-delta": ["text-delta"],
30
+ "tool-input-start": ["tool-input-start"],
31
+ "tool-call": ["tool-input-start", "tool-input-available", "tool-input-error"],
32
+ "tool-result": ["tool-output-available"],
33
+ "tool-error": ["tool-input-start", "tool-input-error"],
34
+ };
35
+
36
+ export function isAlreadyMirroredHostedChunk(
37
+ partType: MirroredPartType,
38
+ mirroredChunkType: DurableMirrorChunkType,
39
+ ): boolean {
40
+ return ALREADY_MIRRORED_CHUNK_TYPES_BY_PART_TYPE[partType]?.includes(mirroredChunkType) ?? false;
41
+ }
42
+
43
+ type MirroredHostedStreamPart = Extract<
44
+ HostedStreamPartForUiChunkMapping,
45
+ | { type: "reasoning-delta" }
46
+ | { type: "text-delta" }
47
+ | { type: "source" }
48
+ | { type: "tool-input-start" }
49
+ | { type: "tool-call" }
50
+ | { type: "tool-input-delta" }
51
+ | { type: "tool-result" }
52
+ | { type: "tool-error" }
53
+ | { type: "error" }
54
+ >;
55
+
56
+ type HostedMirrorBasePart =
57
+ | { type: "reasoning-delta"; text: string }
58
+ | { type: "text-delta"; text: string }
59
+ | { type: "tool-input-start"; toolCallId: string; toolName: string }
60
+ | { type: "tool-input-delta"; toolCallId: string; delta: string }
61
+ | { type: "tool-call"; toolCallId: string; toolName: string; input: unknown }
62
+ | { type: "tool-result"; toolCallId: string; toolName: string; input: unknown; output: unknown }
63
+ | { type: "tool-error"; toolCallId: string; toolName: string; input: unknown; error: Error }
64
+ | { type: "error"; error: Error };
65
+
66
+ type ExtraMirroredHostedStreamPart = Extract<HostedStreamPartForUiChunkMapping, { type: "source" }>;
67
+
68
+ export type HostedChildMirrorPart = HostedMirrorBasePart | ExtraMirroredHostedStreamPart;
69
+
70
+ export function toMirroredHostedStreamPart(
71
+ part: HostedChildMirrorPart,
72
+ ids: {
73
+ messageId: string | null;
74
+ reasoningMessageId: string | null;
75
+ },
76
+ ): MirroredHostedStreamPart {
77
+ switch (part.type) {
78
+ case "reasoning-delta":
79
+ return {
80
+ type: "reasoning-delta",
81
+ id: ids.reasoningMessageId ?? "fork-reasoning",
82
+ text: part.text,
83
+ };
84
+
85
+ case "text-delta":
86
+ return {
87
+ type: "text-delta",
88
+ id: ids.messageId ?? "fork-message",
89
+ text: part.text,
90
+ };
91
+
92
+ case "tool-input-start":
93
+ return {
94
+ type: "tool-input-start",
95
+ id: part.toolCallId,
96
+ toolName: part.toolName,
97
+ };
98
+
99
+ case "source":
100
+ if (part.sourceType === "url") {
101
+ return {
102
+ type: "source",
103
+ id: part.id,
104
+ sourceType: "url",
105
+ url: part.url,
106
+ ...(part.title ? { title: part.title } : {}),
107
+ };
108
+ }
109
+
110
+ return {
111
+ type: "source",
112
+ id: part.id,
113
+ sourceType: "document",
114
+ mediaType: part.mediaType,
115
+ title: part.title ?? part.filename ?? part.id,
116
+ ...(part.filename ? { filename: part.filename } : {}),
117
+ };
118
+
119
+ case "tool-call":
120
+ return {
121
+ type: "tool-call",
122
+ toolCallId: part.toolCallId,
123
+ toolName: part.toolName,
124
+ input: part.input,
125
+ };
126
+
127
+ case "tool-input-delta":
128
+ return {
129
+ type: "tool-input-delta",
130
+ id: part.toolCallId,
131
+ delta: part.delta,
132
+ };
133
+
134
+ case "tool-result":
135
+ return {
136
+ type: "tool-result",
137
+ toolCallId: part.toolCallId,
138
+ toolName: part.toolName,
139
+ input: part.input,
140
+ output: part.output,
141
+ };
142
+
143
+ case "tool-error":
144
+ return {
145
+ type: "tool-error",
146
+ toolCallId: part.toolCallId,
147
+ toolName: part.toolName,
148
+ input: part.input,
149
+ error: part.error,
150
+ };
151
+
152
+ case "error":
153
+ return {
154
+ type: "error",
155
+ error: part.error,
156
+ };
157
+ }
158
+ }
159
+
160
+ export async function appendHostedChildMirrorChunk(input: {
161
+ mirror: HostedChildChunkMirror | null;
162
+ chunk: ChatUiMessageChunk<ChatMessageMetadata>;
163
+ }): Promise<boolean> {
164
+ if (!input.mirror) {
165
+ return false;
166
+ }
167
+
168
+ await input.mirror.handleChunk(input.chunk);
169
+ return true;
170
+ }
171
+
172
+ export async function closeHostedChildReasoningSegment(input: {
173
+ mirror: HostedChildChunkMirror | null;
174
+ reasoningMessageId: string | null;
175
+ state: HostedChildMirrorState;
176
+ }): Promise<void> {
177
+ if (!input.state.reasoningStarted || !input.reasoningMessageId) {
178
+ return;
179
+ }
180
+
181
+ input.state.reasoningStarted = false;
182
+ await appendHostedChildMirrorChunk({
183
+ mirror: input.mirror,
184
+ chunk: {
185
+ type: "reasoning-end",
186
+ id: input.reasoningMessageId,
187
+ },
188
+ });
189
+ }
190
+
191
+ export async function closeHostedChildTextSegment(input: {
192
+ mirror: HostedChildChunkMirror | null;
193
+ messageId: string | null;
194
+ state: HostedChildMirrorState;
195
+ }): Promise<void> {
196
+ if (!input.state.textStarted || !input.messageId) {
197
+ return;
198
+ }
199
+
200
+ input.state.textStarted = false;
201
+ await appendHostedChildMirrorChunk({
202
+ mirror: input.mirror,
203
+ chunk: {
204
+ type: "text-end",
205
+ id: input.messageId,
206
+ },
207
+ });
208
+ }
@@ -359,6 +359,16 @@ export {
359
359
  type HostedChildLifecycleTerminalState,
360
360
  runHostedChildLifecycle,
361
361
  } from "./hosted-child-lifecycle.js";
362
+ export {
363
+ appendHostedChildMirrorChunk,
364
+ closeHostedChildReasoningSegment,
365
+ closeHostedChildTextSegment,
366
+ type HostedChildChunkMirror,
367
+ type HostedChildMirrorPart,
368
+ type HostedChildMirrorState,
369
+ isAlreadyMirroredHostedChunk,
370
+ toMirroredHostedStreamPart,
371
+ } from "./hosted-child-mirror.js";
362
372
  export {
363
373
  type HostedLifecycleAdapter,
364
374
  type HostedLifecycleExecution,
@@ -398,6 +408,11 @@ export {
398
408
  normalizeChatUiMessageChunk,
399
409
  normalizeChatUiMessageStream,
400
410
  } from "../chat/chat-ui-message-helpers.js";
411
+ export {
412
+ type HostedStreamPartForUiChunkMapping,
413
+ type HostedUiChunkMappingOptions,
414
+ mapHostedStreamPartToChatUiChunks,
415
+ } from "../chat/hosted-ui-chunk-mapping.js";
401
416
  export {
402
417
  expandAllowedRemoteToolNames,
403
418
  getProviderNativeToolNames,
@@ -0,0 +1,303 @@
1
+ import type { ChatMessageMetadata, ChatUiMessageChunk } from "./protocol.js";
2
+
3
+ export type HostedUiChunkMappingOptions = {
4
+ messageId?: string | null;
5
+ reasoningMessageId?: string | null;
6
+ sendReasoning?: boolean;
7
+ sendSources?: boolean;
8
+ onError?: (error: unknown) => string;
9
+ };
10
+
11
+ export type HostedStreamSourcePart =
12
+ | {
13
+ type: "source";
14
+ id: string;
15
+ sourceType: "url";
16
+ url: string;
17
+ title?: string;
18
+ }
19
+ | {
20
+ type: "source";
21
+ id: string;
22
+ sourceType: "document";
23
+ mediaType: string;
24
+ title?: string;
25
+ filename?: string;
26
+ };
27
+
28
+ export type HostedStreamPartForUiChunkMapping =
29
+ | {
30
+ type: "start";
31
+ }
32
+ | {
33
+ type: "start-step";
34
+ }
35
+ | {
36
+ type: "finish-step";
37
+ }
38
+ | {
39
+ type: "finish";
40
+ finishReason?: string;
41
+ }
42
+ | {
43
+ type: "abort";
44
+ }
45
+ | {
46
+ type: "reasoning-start";
47
+ id: string;
48
+ }
49
+ | {
50
+ type: "reasoning-delta";
51
+ id: string;
52
+ text: string;
53
+ }
54
+ | {
55
+ type: "reasoning-end";
56
+ id: string;
57
+ }
58
+ | {
59
+ type: "text-start";
60
+ id: string;
61
+ }
62
+ | {
63
+ type: "text-delta";
64
+ id: string;
65
+ text: string;
66
+ }
67
+ | {
68
+ type: "text-end";
69
+ id: string;
70
+ }
71
+ | HostedStreamSourcePart
72
+ | {
73
+ type: "file";
74
+ file: {
75
+ mediaType: string;
76
+ base64: string;
77
+ };
78
+ }
79
+ | {
80
+ type: "tool-input-start";
81
+ id: string;
82
+ toolName: string;
83
+ }
84
+ | {
85
+ type: "tool-input-delta";
86
+ id: string;
87
+ delta: string;
88
+ }
89
+ | {
90
+ type: "tool-call";
91
+ toolCallId: string;
92
+ toolName: string;
93
+ input: unknown;
94
+ invalid: true;
95
+ error: unknown;
96
+ }
97
+ | {
98
+ type: "tool-call";
99
+ toolCallId: string;
100
+ toolName: string;
101
+ input: unknown;
102
+ invalid?: false;
103
+ }
104
+ | {
105
+ type: "tool-approval-request";
106
+ approvalId: string;
107
+ toolCall: {
108
+ toolCallId: string;
109
+ };
110
+ }
111
+ | {
112
+ type: "tool-result";
113
+ toolCallId: string;
114
+ toolName: string;
115
+ input: unknown;
116
+ output: unknown;
117
+ }
118
+ | {
119
+ type: "tool-error";
120
+ toolCallId: string;
121
+ toolName: string;
122
+ input: unknown;
123
+ error: unknown;
124
+ }
125
+ | {
126
+ type: "tool-output-denied";
127
+ toolCallId: string;
128
+ }
129
+ | {
130
+ type: "error";
131
+ error: unknown;
132
+ }
133
+ | {
134
+ type: "tool-input-end";
135
+ }
136
+ | {
137
+ type: "raw";
138
+ };
139
+
140
+ function defaultOnError(error: unknown): string {
141
+ return error instanceof Error ? error.message : String(error);
142
+ }
143
+
144
+ function mapHostedStreamSourceToUiChunks(
145
+ part: HostedStreamSourcePart,
146
+ sendSources: boolean,
147
+ ): ChatUiMessageChunk<ChatMessageMetadata>[] {
148
+ if (!sendSources) {
149
+ return [];
150
+ }
151
+
152
+ if (part.sourceType === "url") {
153
+ return [{
154
+ type: "source-url",
155
+ sourceId: part.id,
156
+ url: part.url,
157
+ ...(part.title ? { title: part.title } : {}),
158
+ }];
159
+ }
160
+
161
+ return [
162
+ {
163
+ type: "source-document",
164
+ sourceId: part.id,
165
+ mediaType: part.mediaType,
166
+ title: part.title ?? part.filename ?? part.id,
167
+ ...(part.filename ? { filename: part.filename } : {}),
168
+ },
169
+ ];
170
+ }
171
+
172
+ function mapToolCallPartToUiChunks(
173
+ part: Extract<HostedStreamPartForUiChunkMapping, { type: "tool-call" }>,
174
+ onError: (error: unknown) => string,
175
+ ): ChatUiMessageChunk<ChatMessageMetadata>[] {
176
+ if (part.invalid) {
177
+ return [{
178
+ type: "tool-input-error",
179
+ toolCallId: part.toolCallId,
180
+ toolName: part.toolName,
181
+ input: part.input,
182
+ errorText: onError(part.error),
183
+ }];
184
+ }
185
+
186
+ return [{
187
+ type: "tool-input-available",
188
+ toolCallId: part.toolCallId,
189
+ toolName: part.toolName,
190
+ input: part.input,
191
+ }];
192
+ }
193
+
194
+ function mapToolErrorPartToUiChunks(
195
+ part: Extract<HostedStreamPartForUiChunkMapping, { type: "tool-error" }>,
196
+ onError: (error: unknown) => string,
197
+ ): ChatUiMessageChunk<ChatMessageMetadata>[] {
198
+ return [
199
+ {
200
+ type: "tool-input-start",
201
+ toolCallId: part.toolCallId,
202
+ toolName: part.toolName,
203
+ },
204
+ {
205
+ type: "tool-input-error",
206
+ toolCallId: part.toolCallId,
207
+ toolName: part.toolName,
208
+ input: part.input,
209
+ errorText: onError(part.error),
210
+ },
211
+ ];
212
+ }
213
+
214
+ export function mapHostedStreamPartToChatUiChunks(
215
+ part: HostedStreamPartForUiChunkMapping,
216
+ options: HostedUiChunkMappingOptions = {},
217
+ ): ChatUiMessageChunk<ChatMessageMetadata>[] {
218
+ const onError = options.onError ?? defaultOnError;
219
+ const sendReasoning = options.sendReasoning ?? true;
220
+ const sendSources = options.sendSources ?? true;
221
+
222
+ switch (part.type) {
223
+ case "start":
224
+ return [{ type: "start", ...(options.messageId ? { messageId: options.messageId } : {}) }];
225
+
226
+ case "start-step":
227
+ return [{ type: "start-step" }];
228
+
229
+ case "finish-step":
230
+ return [{ type: "finish-step" }];
231
+
232
+ case "finish":
233
+ return [{
234
+ type: "finish",
235
+ ...(part.finishReason ? { finishReason: part.finishReason } : {}),
236
+ }];
237
+
238
+ case "abort":
239
+ return [{ type: "abort" }];
240
+
241
+ case "reasoning-start":
242
+ return [{ type: "reasoning-start", id: options.reasoningMessageId ?? part.id }];
243
+
244
+ case "reasoning-delta":
245
+ return sendReasoning
246
+ ? [{ type: "reasoning-delta", id: options.reasoningMessageId ?? part.id, delta: part.text }]
247
+ : [];
248
+
249
+ case "reasoning-end":
250
+ return [{ type: "reasoning-end", id: options.reasoningMessageId ?? part.id }];
251
+
252
+ case "text-start":
253
+ return [{ type: "text-start", id: options.messageId ?? part.id }];
254
+
255
+ case "text-delta":
256
+ return [{ type: "text-delta", id: options.messageId ?? part.id, delta: part.text }];
257
+
258
+ case "text-end":
259
+ return [{ type: "text-end", id: options.messageId ?? part.id }];
260
+
261
+ case "source":
262
+ return mapHostedStreamSourceToUiChunks(part, sendSources);
263
+
264
+ case "file":
265
+ return [{
266
+ type: "file",
267
+ mediaType: part.file.mediaType,
268
+ url: `data:${part.file.mediaType};base64,${part.file.base64}`,
269
+ }];
270
+
271
+ case "tool-input-start":
272
+ return [{ type: "tool-input-start", toolCallId: part.id, toolName: part.toolName }];
273
+
274
+ case "tool-input-delta":
275
+ return [{ type: "tool-input-delta", toolCallId: part.id, inputTextDelta: part.delta }];
276
+
277
+ case "tool-call":
278
+ return mapToolCallPartToUiChunks(part, onError);
279
+
280
+ case "tool-approval-request":
281
+ return [{
282
+ type: "tool-approval-request",
283
+ approvalId: part.approvalId,
284
+ toolCallId: part.toolCall.toolCallId,
285
+ }];
286
+
287
+ case "tool-result":
288
+ return [{ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output }];
289
+
290
+ case "tool-error":
291
+ return mapToolErrorPartToUiChunks(part, onError);
292
+
293
+ case "tool-output-denied":
294
+ return [{ type: "tool-output-denied", toolCallId: part.toolCallId }];
295
+
296
+ case "error":
297
+ return [{ type: "error", errorText: onError(part.error) }];
298
+
299
+ case "tool-input-end":
300
+ case "raw":
301
+ return [];
302
+ }
303
+ }
@@ -236,6 +236,11 @@ export {
236
236
  normalizeChatUiMessageChunk,
237
237
  normalizeChatUiMessageStream,
238
238
  } from "./chat-ui-message-helpers.js";
239
+ export {
240
+ type HostedStreamPartForUiChunkMapping,
241
+ type HostedUiChunkMappingOptions,
242
+ mapHostedStreamPartToChatUiChunks,
243
+ } from "./hosted-ui-chunk-mapping.js";
239
244
 
240
245
  export {
241
246
  useCompletion,
@@ -0,0 +1,61 @@
1
+ import { readRecord } from "./provider-records.js";
2
+
3
+ function isNumberArray(value: unknown): value is number[] {
4
+ return Array.isArray(value) && value.every((entry) => typeof entry === "number");
5
+ }
6
+
7
+ export function extractOpenAIEmbeddings(payload: unknown): number[][] {
8
+ const record = readRecord(payload);
9
+ const data = record?.data;
10
+ if (!Array.isArray(data)) {
11
+ throw new Error("Invalid OpenAI embedding response: data array missing");
12
+ }
13
+
14
+ const embeddings: number[][] = [];
15
+
16
+ for (const item of data) {
17
+ const itemRecord = readRecord(item);
18
+ const embedding = itemRecord?.embedding;
19
+ if (!isNumberArray(embedding)) {
20
+ throw new Error("Invalid OpenAI embedding response: embedding vector missing");
21
+ }
22
+ embeddings.push(embedding);
23
+ }
24
+
25
+ return embeddings;
26
+ }
27
+
28
+ export function extractOpenAIUsageTokens(payload: unknown): number | undefined {
29
+ const record = readRecord(payload);
30
+ const usage = readRecord(record?.usage);
31
+ const totalTokens = usage?.total_tokens;
32
+ return typeof totalTokens === "number" ? totalTokens : undefined;
33
+ }
34
+
35
+ export function extractGoogleEmbedding(payload: unknown): number[] {
36
+ const record = readRecord(payload);
37
+ const embeddings = record?.embeddings;
38
+
39
+ if (Array.isArray(embeddings) && embeddings.length > 0) {
40
+ const firstEmbedding = readRecord(embeddings[0]);
41
+ const values = firstEmbedding?.values;
42
+ if (isNumberArray(values)) {
43
+ return values;
44
+ }
45
+ }
46
+
47
+ const embedding = readRecord(record?.embedding);
48
+ const values = embedding?.values;
49
+ if (isNumberArray(values)) {
50
+ return values;
51
+ }
52
+
53
+ throw new Error("Invalid Google embedding response: embedding vector missing");
54
+ }
55
+
56
+ export function extractGoogleUsageTokens(payload: unknown): number | undefined {
57
+ const record = readRecord(payload);
58
+ const usageMetadata = readRecord(record?.usageMetadata);
59
+ const promptTokenCount = usageMetadata?.promptTokenCount;
60
+ return typeof promptTokenCount === "number" ? promptTokenCount : undefined;
61
+ }
@@ -0,0 +1,69 @@
1
+ export type NormalizedFinishReason = string | { unified: string; raw: string } | null;
2
+
3
+ export function normalizeAnthropicFinishReason(raw: unknown): NormalizedFinishReason {
4
+ if (typeof raw !== "string") {
5
+ return null;
6
+ }
7
+
8
+ switch (raw) {
9
+ case "tool_use":
10
+ return { unified: "tool-calls", raw };
11
+ case "end_turn":
12
+ case "stop_sequence":
13
+ return { unified: "stop", raw };
14
+ case "max_tokens":
15
+ return { unified: "length", raw };
16
+ default:
17
+ return raw;
18
+ }
19
+ }
20
+
21
+ export function normalizeGoogleFinishReason(raw: unknown): NormalizedFinishReason {
22
+ if (typeof raw !== "string") {
23
+ return null;
24
+ }
25
+
26
+ switch (raw) {
27
+ case "STOP":
28
+ return { unified: "stop", raw };
29
+ case "MAX_TOKENS":
30
+ return { unified: "length", raw };
31
+ case "SAFETY":
32
+ case "RECITATION":
33
+ return { unified: "content-filter", raw };
34
+ default:
35
+ return raw.toLowerCase();
36
+ }
37
+ }
38
+
39
+ export function normalizeOpenAIFinishReason(raw: unknown): NormalizedFinishReason {
40
+ if (typeof raw !== "string") {
41
+ return null;
42
+ }
43
+
44
+ if (raw === "tool_calls") {
45
+ return { unified: "tool-calls", raw };
46
+ }
47
+
48
+ if (raw === "content_filter") {
49
+ return { unified: "content-filter", raw };
50
+ }
51
+
52
+ return raw;
53
+ }
54
+
55
+ export function normalizeOpenAIResponsesFinishReason(raw: unknown): NormalizedFinishReason {
56
+ if (typeof raw !== "string") return null;
57
+ switch (raw) {
58
+ case "completed":
59
+ return { unified: "stop", raw };
60
+ case "incomplete":
61
+ return { unified: "length", raw };
62
+ case "failed":
63
+ return { unified: "error", raw };
64
+ case "in_progress":
65
+ return null;
66
+ default:
67
+ return raw;
68
+ }
69
+ }
@@ -0,0 +1,29 @@
1
+ export function parseSseChunk(chunk: string): {
2
+ events: Array<unknown | "[DONE]">;
3
+ remainder: string;
4
+ } {
5
+ const blocks = chunk.split(/\r?\n\r?\n/);
6
+ const remainder = blocks.pop() ?? "";
7
+ const events = blocks.flatMap((block) => {
8
+ const dataLines = block.split(/\r?\n/)
9
+ .filter((line) => line.startsWith("data:"))
10
+ .map((line) => line.slice(5).trimStart());
11
+
12
+ if (!dataLines.length) {
13
+ return [];
14
+ }
15
+
16
+ const payload = dataLines.join("\n").trim();
17
+ if (payload === "[DONE]") {
18
+ return ["[DONE]" as const];
19
+ }
20
+
21
+ try {
22
+ return [JSON.parse(payload) as unknown];
23
+ } catch {
24
+ return [];
25
+ }
26
+ });
27
+
28
+ return { events, remainder };
29
+ }