@kernl-sdk/ai 0.1.1 → 0.1.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +9 -0
- package/dist/__tests__/integration.test.js +137 -0
- package/dist/__tests__/language-model.test.d.ts +2 -0
- package/dist/__tests__/language-model.test.d.ts.map +1 -0
- package/dist/__tests__/language-model.test.js +423 -0
- package/dist/convert/__tests__/message.test.js +1 -1
- package/dist/convert/message.js +1 -1
- package/dist/convert/response.js +1 -1
- package/dist/convert/stream.d.ts.map +1 -1
- package/dist/convert/stream.js +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/language-model.d.ts.map +1 -1
- package/dist/language-model.js +61 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +1 -1
- package/package.json +4 -4
- package/src/__tests__/integration.test.ts +161 -0
- package/src/__tests__/language-model.test.ts +465 -0
- package/src/convert/__tests__/message.test.ts +1 -1
- package/src/convert/message.ts +1 -1
- package/src/convert/response.ts +1 -1
- package/src/convert/stream.ts +5 -3
- package/src/index.ts +1 -1
- package/src/language-model.ts +68 -1
- package/src/providers/anthropic.ts +1 -1
- package/src/providers/google.ts +1 -1
- package/src/providers/openai.ts +1 -1
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
LanguageModelV3,
|
|
4
|
+
LanguageModelV3StreamPart,
|
|
5
|
+
} from "@ai-sdk/provider";
|
|
6
|
+
|
|
7
|
+
import { AISDKLanguageModel } from "../language-model";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Unit tests for AISDKLanguageModel stream accumulation behavior
|
|
11
|
+
*/
|
|
12
|
+
describe("AISDKLanguageModel", () => {
|
|
13
|
+
describe("stream - delta accumulation", () => {
|
|
14
|
+
it("should yield delta events and complete Message on text-end", async () => {
|
|
15
|
+
// Mock the underlying AI SDK model
|
|
16
|
+
const mockModel: LanguageModelV3 = {
|
|
17
|
+
provider: "test",
|
|
18
|
+
modelId: "test-model",
|
|
19
|
+
doStream: vi.fn().mockResolvedValue({
|
|
20
|
+
stream: new ReadableStream({
|
|
21
|
+
start(controller) {
|
|
22
|
+
const parts: LanguageModelV3StreamPart[] = [
|
|
23
|
+
{
|
|
24
|
+
type: "text-start",
|
|
25
|
+
id: "text-1",
|
|
26
|
+
providerMetadata: undefined,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "text-delta",
|
|
30
|
+
id: "text-1",
|
|
31
|
+
delta: "Hello",
|
|
32
|
+
providerMetadata: undefined,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "text-delta",
|
|
36
|
+
id: "text-1",
|
|
37
|
+
delta: " ",
|
|
38
|
+
providerMetadata: undefined,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "text-delta",
|
|
42
|
+
id: "text-1",
|
|
43
|
+
delta: "World",
|
|
44
|
+
providerMetadata: undefined,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "text-end",
|
|
48
|
+
id: "text-1",
|
|
49
|
+
providerMetadata: { test: { foo: "bar" } },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "finish",
|
|
53
|
+
finishReason: "stop",
|
|
54
|
+
usage: {
|
|
55
|
+
inputTokens: 5,
|
|
56
|
+
outputTokens: 10,
|
|
57
|
+
totalTokens: 15,
|
|
58
|
+
},
|
|
59
|
+
providerMetadata: undefined,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const part of parts) {
|
|
64
|
+
controller.enqueue(part);
|
|
65
|
+
}
|
|
66
|
+
controller.close();
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
} as any;
|
|
71
|
+
|
|
72
|
+
const model = new AISDKLanguageModel(mockModel);
|
|
73
|
+
|
|
74
|
+
const events = [];
|
|
75
|
+
for await (const event of model.stream({
|
|
76
|
+
input: [
|
|
77
|
+
{
|
|
78
|
+
kind: "message",
|
|
79
|
+
role: "user",
|
|
80
|
+
id: "msg-1",
|
|
81
|
+
content: [{ kind: "text", text: "Hi" }],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
settings: {},
|
|
85
|
+
})) {
|
|
86
|
+
events.push(event);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Should have: start, 3 deltas, end, complete Message, finish
|
|
90
|
+
expect(events).toHaveLength(7);
|
|
91
|
+
|
|
92
|
+
// Check delta events
|
|
93
|
+
expect(events[0]).toMatchObject({ kind: "text-start", id: "text-1" });
|
|
94
|
+
expect(events[1]).toMatchObject({
|
|
95
|
+
kind: "text-delta",
|
|
96
|
+
id: "text-1",
|
|
97
|
+
text: "Hello",
|
|
98
|
+
});
|
|
99
|
+
expect(events[2]).toMatchObject({
|
|
100
|
+
kind: "text-delta",
|
|
101
|
+
id: "text-1",
|
|
102
|
+
text: " ",
|
|
103
|
+
});
|
|
104
|
+
expect(events[3]).toMatchObject({
|
|
105
|
+
kind: "text-delta",
|
|
106
|
+
id: "text-1",
|
|
107
|
+
text: "World",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Check complete Message item (yielded before end event)
|
|
111
|
+
const messageEvent = events[4];
|
|
112
|
+
expect(messageEvent.kind).toBe("message");
|
|
113
|
+
expect(messageEvent).toMatchObject({
|
|
114
|
+
kind: "message",
|
|
115
|
+
role: "assistant",
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
kind: "text",
|
|
119
|
+
text: "Hello World", // Accumulated text
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
providerMetadata: { test: { foo: "bar" } }, // From end event
|
|
123
|
+
});
|
|
124
|
+
expect(messageEvent.id).toBeDefined();
|
|
125
|
+
|
|
126
|
+
// Check end event (yielded after Message)
|
|
127
|
+
expect(events[5]).toMatchObject({ kind: "text-end", id: "text-1" });
|
|
128
|
+
|
|
129
|
+
// Check finish event
|
|
130
|
+
expect(events[6]).toMatchObject({ kind: "finish" });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should yield delta events and complete Reasoning on reasoning-end", async () => {
|
|
134
|
+
const mockModel: LanguageModelV3 = {
|
|
135
|
+
provider: "test",
|
|
136
|
+
modelId: "test-model",
|
|
137
|
+
doStream: vi.fn().mockResolvedValue({
|
|
138
|
+
stream: new ReadableStream({
|
|
139
|
+
start(controller) {
|
|
140
|
+
const parts: LanguageModelV3StreamPart[] = [
|
|
141
|
+
{
|
|
142
|
+
type: "reasoning-start",
|
|
143
|
+
id: "reason-1",
|
|
144
|
+
providerMetadata: undefined,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: "reasoning-delta",
|
|
148
|
+
id: "reason-1",
|
|
149
|
+
delta: "Let me think",
|
|
150
|
+
providerMetadata: undefined,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: "reasoning-delta",
|
|
154
|
+
id: "reason-1",
|
|
155
|
+
delta: " about this",
|
|
156
|
+
providerMetadata: undefined,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: "reasoning-end",
|
|
160
|
+
id: "reason-1",
|
|
161
|
+
providerMetadata: { test: { reasoning: true } },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: "finish",
|
|
165
|
+
finishReason: "stop",
|
|
166
|
+
usage: {
|
|
167
|
+
inputTokens: 5,
|
|
168
|
+
outputTokens: 20,
|
|
169
|
+
totalTokens: 25,
|
|
170
|
+
},
|
|
171
|
+
providerMetadata: undefined,
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const part of parts) {
|
|
176
|
+
controller.enqueue(part);
|
|
177
|
+
}
|
|
178
|
+
controller.close();
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
}),
|
|
182
|
+
} as any;
|
|
183
|
+
|
|
184
|
+
const model = new AISDKLanguageModel(mockModel);
|
|
185
|
+
|
|
186
|
+
const events = [];
|
|
187
|
+
for await (const event of model.stream({
|
|
188
|
+
input: [
|
|
189
|
+
{
|
|
190
|
+
kind: "message",
|
|
191
|
+
role: "user",
|
|
192
|
+
id: "msg-1",
|
|
193
|
+
content: [{ kind: "text", text: "Think about this" }],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
settings: {},
|
|
197
|
+
})) {
|
|
198
|
+
events.push(event);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Should have: start, 2 deltas, end, complete Reasoning, finish
|
|
202
|
+
expect(events).toHaveLength(6);
|
|
203
|
+
|
|
204
|
+
// Check delta events
|
|
205
|
+
expect(events[0]).toMatchObject({
|
|
206
|
+
kind: "reasoning-start",
|
|
207
|
+
id: "reason-1",
|
|
208
|
+
});
|
|
209
|
+
expect(events[1]).toMatchObject({
|
|
210
|
+
kind: "reasoning-delta",
|
|
211
|
+
id: "reason-1",
|
|
212
|
+
text: "Let me think",
|
|
213
|
+
});
|
|
214
|
+
expect(events[2]).toMatchObject({
|
|
215
|
+
kind: "reasoning-delta",
|
|
216
|
+
id: "reason-1",
|
|
217
|
+
text: " about this",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Check complete Reasoning item (yielded before end event)
|
|
221
|
+
const reasoningEvent = events[3];
|
|
222
|
+
expect(reasoningEvent.kind).toBe("reasoning");
|
|
223
|
+
expect(reasoningEvent).toMatchObject({
|
|
224
|
+
kind: "reasoning",
|
|
225
|
+
text: "Let me think about this", // Accumulated text
|
|
226
|
+
providerMetadata: { test: { reasoning: true } }, // From end event
|
|
227
|
+
});
|
|
228
|
+
expect(reasoningEvent.id).toBeDefined();
|
|
229
|
+
|
|
230
|
+
// Check end event (yielded after Reasoning)
|
|
231
|
+
expect(events[4]).toMatchObject({
|
|
232
|
+
kind: "reasoning-end",
|
|
233
|
+
id: "reason-1",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Check finish event
|
|
237
|
+
expect(events[5]).toMatchObject({ kind: "finish" });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should handle multiple text blocks with different IDs", async () => {
|
|
241
|
+
const mockModel: LanguageModelV3 = {
|
|
242
|
+
provider: "test",
|
|
243
|
+
modelId: "test-model",
|
|
244
|
+
doStream: vi.fn().mockResolvedValue({
|
|
245
|
+
stream: new ReadableStream({
|
|
246
|
+
start(controller) {
|
|
247
|
+
const parts: LanguageModelV3StreamPart[] = [
|
|
248
|
+
{ type: "text-start", id: "text-1", providerMetadata: undefined },
|
|
249
|
+
{
|
|
250
|
+
type: "text-delta",
|
|
251
|
+
id: "text-1",
|
|
252
|
+
delta: "First",
|
|
253
|
+
providerMetadata: undefined,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: "text-end",
|
|
257
|
+
id: "text-1",
|
|
258
|
+
providerMetadata: { test: { order: 1 } },
|
|
259
|
+
},
|
|
260
|
+
{ type: "text-start", id: "text-2", providerMetadata: undefined },
|
|
261
|
+
{
|
|
262
|
+
type: "text-delta",
|
|
263
|
+
id: "text-2",
|
|
264
|
+
delta: "Second",
|
|
265
|
+
providerMetadata: undefined,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: "text-end",
|
|
269
|
+
id: "text-2",
|
|
270
|
+
providerMetadata: { test: { order: 2 } },
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
type: "finish",
|
|
274
|
+
finishReason: "stop",
|
|
275
|
+
usage: {
|
|
276
|
+
inputTokens: 5,
|
|
277
|
+
outputTokens: 10,
|
|
278
|
+
totalTokens: 15,
|
|
279
|
+
},
|
|
280
|
+
providerMetadata: undefined,
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (const part of parts) {
|
|
285
|
+
controller.enqueue(part);
|
|
286
|
+
}
|
|
287
|
+
controller.close();
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
}),
|
|
291
|
+
} as any;
|
|
292
|
+
|
|
293
|
+
const model = new AISDKLanguageModel(mockModel);
|
|
294
|
+
|
|
295
|
+
const events = [];
|
|
296
|
+
for await (const event of model.stream({
|
|
297
|
+
input: [
|
|
298
|
+
{
|
|
299
|
+
kind: "message",
|
|
300
|
+
role: "user",
|
|
301
|
+
id: "msg-1",
|
|
302
|
+
content: [{ kind: "text", text: "Hi" }],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
settings: {},
|
|
306
|
+
})) {
|
|
307
|
+
events.push(event);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Check we got two separate Message items
|
|
311
|
+
const messageEvents = events.filter((e) => e.kind === "message");
|
|
312
|
+
expect(messageEvents).toHaveLength(2);
|
|
313
|
+
|
|
314
|
+
expect(messageEvents[0]).toMatchObject({
|
|
315
|
+
kind: "message",
|
|
316
|
+
role: "assistant",
|
|
317
|
+
content: [{ kind: "text", text: "First" }],
|
|
318
|
+
providerMetadata: { test: { order: 1 } },
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(messageEvents[1]).toMatchObject({
|
|
322
|
+
kind: "message",
|
|
323
|
+
role: "assistant",
|
|
324
|
+
content: [{ kind: "text", text: "Second" }],
|
|
325
|
+
providerMetadata: { test: { order: 2 } },
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should use metadata from end event (last-wins)", async () => {
|
|
330
|
+
const mockModel: LanguageModelV3 = {
|
|
331
|
+
provider: "test",
|
|
332
|
+
modelId: "test-model",
|
|
333
|
+
doStream: vi.fn().mockResolvedValue({
|
|
334
|
+
stream: new ReadableStream({
|
|
335
|
+
start(controller) {
|
|
336
|
+
const parts: LanguageModelV3StreamPart[] = [
|
|
337
|
+
{
|
|
338
|
+
type: "text-start",
|
|
339
|
+
id: "text-1",
|
|
340
|
+
providerMetadata: { test: { version: "start" } },
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
type: "text-delta",
|
|
344
|
+
id: "text-1",
|
|
345
|
+
delta: "Test",
|
|
346
|
+
providerMetadata: { test: { version: "delta" } },
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: "text-end",
|
|
350
|
+
id: "text-1",
|
|
351
|
+
providerMetadata: { test: { version: "end" } },
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
type: "finish",
|
|
355
|
+
finishReason: "stop",
|
|
356
|
+
usage: {
|
|
357
|
+
inputTokens: 5,
|
|
358
|
+
outputTokens: 10,
|
|
359
|
+
totalTokens: 15,
|
|
360
|
+
},
|
|
361
|
+
providerMetadata: undefined,
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
for (const part of parts) {
|
|
366
|
+
controller.enqueue(part);
|
|
367
|
+
}
|
|
368
|
+
controller.close();
|
|
369
|
+
},
|
|
370
|
+
}),
|
|
371
|
+
}),
|
|
372
|
+
} as any;
|
|
373
|
+
|
|
374
|
+
const model = new AISDKLanguageModel(mockModel);
|
|
375
|
+
|
|
376
|
+
const events = [];
|
|
377
|
+
for await (const event of model.stream({
|
|
378
|
+
input: [
|
|
379
|
+
{
|
|
380
|
+
kind: "message",
|
|
381
|
+
role: "user",
|
|
382
|
+
id: "msg-1",
|
|
383
|
+
content: [{ kind: "text", text: "Hi" }],
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
settings: {},
|
|
387
|
+
})) {
|
|
388
|
+
events.push(event);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check complete Message has metadata from end event
|
|
392
|
+
const messageEvent = events.find((e) => e.kind === "message");
|
|
393
|
+
expect(messageEvent).toMatchObject({
|
|
394
|
+
providerMetadata: { test: { version: "end" } }, // From end event, not start/delta
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should pass through tool-call events unchanged", async () => {
|
|
399
|
+
const mockModel: LanguageModelV3 = {
|
|
400
|
+
provider: "test",
|
|
401
|
+
modelId: "test-model",
|
|
402
|
+
doStream: vi.fn().mockResolvedValue({
|
|
403
|
+
stream: new ReadableStream({
|
|
404
|
+
start(controller) {
|
|
405
|
+
const parts: LanguageModelV3StreamPart[] = [
|
|
406
|
+
{
|
|
407
|
+
type: "tool-call",
|
|
408
|
+
toolCallId: "call-123",
|
|
409
|
+
toolName: "calculator",
|
|
410
|
+
input: '{"expression":"2+2"}',
|
|
411
|
+
providerMetadata: undefined,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
type: "finish",
|
|
415
|
+
finishReason: "tool-calls",
|
|
416
|
+
usage: {
|
|
417
|
+
inputTokens: 5,
|
|
418
|
+
outputTokens: 10,
|
|
419
|
+
totalTokens: 15,
|
|
420
|
+
},
|
|
421
|
+
providerMetadata: undefined,
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
for (const part of parts) {
|
|
426
|
+
controller.enqueue(part);
|
|
427
|
+
}
|
|
428
|
+
controller.close();
|
|
429
|
+
},
|
|
430
|
+
}),
|
|
431
|
+
}),
|
|
432
|
+
} as any;
|
|
433
|
+
|
|
434
|
+
const model = new AISDKLanguageModel(mockModel);
|
|
435
|
+
|
|
436
|
+
const events = [];
|
|
437
|
+
for await (const event of model.stream({
|
|
438
|
+
input: [
|
|
439
|
+
{
|
|
440
|
+
kind: "message",
|
|
441
|
+
role: "user",
|
|
442
|
+
id: "msg-1",
|
|
443
|
+
content: [{ kind: "text", text: "Calculate 2+2" }],
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
settings: {},
|
|
447
|
+
})) {
|
|
448
|
+
events.push(event);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Should have tool-call and finish
|
|
452
|
+
expect(events).toHaveLength(2);
|
|
453
|
+
|
|
454
|
+
expect(events[0]).toMatchObject({
|
|
455
|
+
kind: "tool-call",
|
|
456
|
+
callId: "call-123",
|
|
457
|
+
toolId: "calculator",
|
|
458
|
+
state: "completed",
|
|
459
|
+
arguments: '{"expression":"2+2"}',
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(events[1]).toMatchObject({ kind: "finish" });
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|
package/src/convert/message.ts
CHANGED
|
@@ -112,7 +112,7 @@ export const MESSAGE: Codec<LanguageModelItem, LanguageModelV3Message> = {
|
|
|
112
112
|
type: "tool-call",
|
|
113
113
|
toolCallId: item.callId,
|
|
114
114
|
toolName: item.toolId,
|
|
115
|
-
input:
|
|
115
|
+
input: item.arguments,
|
|
116
116
|
providerOptions: item.providerMetadata,
|
|
117
117
|
},
|
|
118
118
|
],
|
package/src/convert/response.ts
CHANGED
|
@@ -62,7 +62,7 @@ export const MODEL_RESPONSE: Codec<LanguageModelResponse, AISdkGenerateResult> =
|
|
|
62
62
|
callId: item.toolCallId,
|
|
63
63
|
toolId: item.toolName,
|
|
64
64
|
state: "completed",
|
|
65
|
-
arguments:
|
|
65
|
+
arguments: item.input,
|
|
66
66
|
providerMetadata: item.providerMetadata,
|
|
67
67
|
});
|
|
68
68
|
} else if (item.type === "file") {
|
package/src/convert/stream.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Codec, LanguageModelStreamEvent } from "@kernl-sdk/protocol";
|
|
2
2
|
import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
|
|
3
|
+
import { COMPLETED, FAILED } from "@kernl-sdk/protocol";
|
|
3
4
|
import { WARNING } from "./response";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -109,8 +110,9 @@ export const STREAM_PART: Codec<
|
|
|
109
110
|
case "tool-call":
|
|
110
111
|
return {
|
|
111
112
|
kind: "tool-call",
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
callId: part.toolCallId,
|
|
114
|
+
toolId: part.toolName,
|
|
115
|
+
state: COMPLETED,
|
|
114
116
|
arguments: part.input,
|
|
115
117
|
providerMetadata: part.providerMetadata,
|
|
116
118
|
};
|
|
@@ -121,7 +123,7 @@ export const STREAM_PART: Codec<
|
|
|
121
123
|
kind: "tool-result",
|
|
122
124
|
callId: part.toolCallId,
|
|
123
125
|
toolId: part.toolName,
|
|
124
|
-
state: part.isError ?
|
|
126
|
+
state: part.isError ? FAILED : COMPLETED,
|
|
125
127
|
result: part.result,
|
|
126
128
|
error: part.isError ? String(part.result) : null,
|
|
127
129
|
providerMetadata: part.providerMetadata,
|
package/src/index.ts
CHANGED
package/src/language-model.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
LanguageModelResponse,
|
|
7
7
|
LanguageModelStreamEvent,
|
|
8
8
|
} from "@kernl-sdk/protocol";
|
|
9
|
+
import { message, reasoning } from "@kernl-sdk/protocol";
|
|
9
10
|
|
|
10
11
|
import { MESSAGE } from "./convert/message";
|
|
11
12
|
import { TOOL } from "./convert/tools";
|
|
@@ -69,7 +70,73 @@ export class AISDKLanguageModel implements LanguageModel {
|
|
|
69
70
|
abortSignal: request.abort,
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
// text and reasoning buffers for delta accumulation
|
|
74
|
+
const tbuf: Record<string, string> = {};
|
|
75
|
+
const rbuf: Record<string, string> = {};
|
|
76
|
+
|
|
77
|
+
for await (const event of convertStream(stream.stream)) {
|
|
78
|
+
switch (event.kind) {
|
|
79
|
+
case "text-start": {
|
|
80
|
+
tbuf[event.id] = "";
|
|
81
|
+
yield event;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "text-delta": {
|
|
86
|
+
if (tbuf[event.id] !== undefined) {
|
|
87
|
+
tbuf[event.id] += event.text;
|
|
88
|
+
}
|
|
89
|
+
yield event;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case "text-end": {
|
|
94
|
+
const text = tbuf[event.id];
|
|
95
|
+
if (text !== undefined) {
|
|
96
|
+
yield message({
|
|
97
|
+
role: "assistant",
|
|
98
|
+
text,
|
|
99
|
+
providerMetadata: event.providerMetadata,
|
|
100
|
+
});
|
|
101
|
+
delete tbuf[event.id];
|
|
102
|
+
}
|
|
103
|
+
yield event;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case "reasoning-start": {
|
|
108
|
+
rbuf[event.id] = "";
|
|
109
|
+
yield event;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "reasoning-delta": {
|
|
114
|
+
if (rbuf[event.id] !== undefined) {
|
|
115
|
+
rbuf[event.id] += event.text;
|
|
116
|
+
}
|
|
117
|
+
yield event;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "reasoning-end": {
|
|
122
|
+
const text = rbuf[event.id];
|
|
123
|
+
if (text !== undefined) {
|
|
124
|
+
yield reasoning({
|
|
125
|
+
text,
|
|
126
|
+
providerMetadata: event.providerMetadata,
|
|
127
|
+
});
|
|
128
|
+
delete rbuf[event.id];
|
|
129
|
+
}
|
|
130
|
+
yield event;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
// all other events (tool-call, tool-result, finish, etc.) pass through
|
|
136
|
+
yield event;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
73
140
|
} catch (error) {
|
|
74
141
|
throw wrapError(error, "stream");
|
|
75
142
|
}
|
|
@@ -2,7 +2,7 @@ import { anthropic as createAnthropicModel } from "@ai-sdk/anthropic";
|
|
|
2
2
|
import { AISDKLanguageModel } from "../language-model";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Create a
|
|
5
|
+
* Create a kernl-compatible Anthropic language model.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
package/src/providers/google.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { google as createGoogleModel } from "@ai-sdk/google";
|
|
|
2
2
|
import { AISDKLanguageModel } from "../language-model";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Create a
|
|
5
|
+
* Create a kernl-compatible Google Generative AI language model.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
package/src/providers/openai.ts
CHANGED