@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
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language-model.d.ts","sourceRoot":"","sources":["../src/language-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"language-model.d.ts","sourceRoot":"","sources":["../src/language-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,qBAAqB,CAAC;AAU7B;;GAEG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IAK1C,OAAO,CAAC,KAAK;IAJzB,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEL,KAAK,EAAE,eAAe;IAK1C;;OAEG;IACG,QAAQ,CACZ,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAmBjC;;OAEG;IACI,MAAM,CACX,OAAO,EAAE,oBAAoB,GAC5B,aAAa,CAAC,wBAAwB,CAAC;CAoF3C"}
|
package/dist/language-model.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { message, reasoning } from "@kernl-sdk/protocol";
|
|
1
2
|
import { MESSAGE } from "./convert/message";
|
|
2
3
|
import { TOOL } from "./convert/tools";
|
|
3
4
|
import { MODEL_SETTINGS } from "./convert/settings";
|
|
@@ -51,7 +52,66 @@ export class AISDKLanguageModel {
|
|
|
51
52
|
...settings,
|
|
52
53
|
abortSignal: request.abort,
|
|
53
54
|
});
|
|
54
|
-
|
|
55
|
+
// text and reasoning buffers for delta accumulation
|
|
56
|
+
const tbuf = {};
|
|
57
|
+
const rbuf = {};
|
|
58
|
+
for await (const event of convertStream(stream.stream)) {
|
|
59
|
+
switch (event.kind) {
|
|
60
|
+
case "text-start": {
|
|
61
|
+
tbuf[event.id] = "";
|
|
62
|
+
yield event;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "text-delta": {
|
|
66
|
+
if (tbuf[event.id] !== undefined) {
|
|
67
|
+
tbuf[event.id] += event.text;
|
|
68
|
+
}
|
|
69
|
+
yield event;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "text-end": {
|
|
73
|
+
const text = tbuf[event.id];
|
|
74
|
+
if (text !== undefined) {
|
|
75
|
+
yield message({
|
|
76
|
+
role: "assistant",
|
|
77
|
+
text,
|
|
78
|
+
providerMetadata: event.providerMetadata,
|
|
79
|
+
});
|
|
80
|
+
delete tbuf[event.id];
|
|
81
|
+
}
|
|
82
|
+
yield event;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "reasoning-start": {
|
|
86
|
+
rbuf[event.id] = "";
|
|
87
|
+
yield event;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "reasoning-delta": {
|
|
91
|
+
if (rbuf[event.id] !== undefined) {
|
|
92
|
+
rbuf[event.id] += event.text;
|
|
93
|
+
}
|
|
94
|
+
yield event;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "reasoning-end": {
|
|
98
|
+
const text = rbuf[event.id];
|
|
99
|
+
if (text !== undefined) {
|
|
100
|
+
yield reasoning({
|
|
101
|
+
text,
|
|
102
|
+
providerMetadata: event.providerMetadata,
|
|
103
|
+
});
|
|
104
|
+
delete rbuf[event.id];
|
|
105
|
+
}
|
|
106
|
+
yield event;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
default:
|
|
110
|
+
// all other events (tool-call, tool-result, finish, etc.) pass through
|
|
111
|
+
yield event;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
55
115
|
}
|
|
56
116
|
catch (error) {
|
|
57
117
|
throw wrapError(error, "stream");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { anthropic as createAnthropicModel } from "@ai-sdk/anthropic";
|
|
2
2
|
import { AISDKLanguageModel } from "../language-model";
|
|
3
3
|
/**
|
|
4
|
-
* Create a
|
|
4
|
+
* Create a kernl-compatible Anthropic language model.
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```ts
|
package/dist/providers/google.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { google as createGoogleModel } from "@ai-sdk/google";
|
|
2
2
|
import { AISDKLanguageModel } from "../language-model";
|
|
3
3
|
/**
|
|
4
|
-
* Create a
|
|
4
|
+
* Create a kernl-compatible Google Generative AI language model.
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```ts
|
package/dist/providers/openai.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernl-sdk/ai",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "AI SDK adapter for
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Vercel AI SDK adapter for kernl",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kernl",
|
|
7
7
|
"ai",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@ai-sdk/provider": "^3.0.0-beta.15",
|
|
44
|
-
"@kernl-sdk/protocol": "0.
|
|
44
|
+
"@kernl-sdk/protocol": "0.2.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"@ai-sdk/anthropic": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vitest": "^4.0.8"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@kernl-sdk/shared": "^0.1.
|
|
68
|
+
"@kernl-sdk/shared": "^0.1.2"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsc && tsc-alias",
|
|
@@ -257,6 +257,70 @@ describe.skipIf(SKIP_INTEGRATION_TESTS)(
|
|
|
257
257
|
const finishEvents = events.filter((e) => e.kind === "finish");
|
|
258
258
|
expect(finishEvents.length).toBe(1);
|
|
259
259
|
});
|
|
260
|
+
|
|
261
|
+
it("should yield both delta events and complete Message items", async () => {
|
|
262
|
+
const events = [];
|
|
263
|
+
|
|
264
|
+
for await (const event of gpt4omini.stream({
|
|
265
|
+
input: [
|
|
266
|
+
{
|
|
267
|
+
kind: "message",
|
|
268
|
+
role: "user",
|
|
269
|
+
id: "msg-1",
|
|
270
|
+
content: [
|
|
271
|
+
{ kind: "text", text: "Say 'Hello World' and nothing else." },
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
settings: {
|
|
276
|
+
maxTokens: 50,
|
|
277
|
+
temperature: 0,
|
|
278
|
+
},
|
|
279
|
+
})) {
|
|
280
|
+
events.push(event);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
expect(events.length).toBeGreaterThan(0);
|
|
284
|
+
|
|
285
|
+
// Should have text-delta events (for streaming UX)
|
|
286
|
+
const textDeltas = events.filter((e) => e.kind === "text-delta");
|
|
287
|
+
expect(textDeltas.length).toBeGreaterThan(0);
|
|
288
|
+
|
|
289
|
+
// Should have text-start event
|
|
290
|
+
const textStarts = events.filter((e) => e.kind === "text-start");
|
|
291
|
+
expect(textStarts.length).toBeGreaterThan(0);
|
|
292
|
+
|
|
293
|
+
// Should have text-end event
|
|
294
|
+
const textEnds = events.filter((e) => e.kind === "text-end");
|
|
295
|
+
expect(textEnds.length).toBeGreaterThan(0);
|
|
296
|
+
|
|
297
|
+
// Should have complete Message item (for history)
|
|
298
|
+
const messages = events.filter((e) => e.kind === "message");
|
|
299
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
300
|
+
|
|
301
|
+
const assistantMessage = messages[0] as any;
|
|
302
|
+
expect(assistantMessage.role).toBe("assistant");
|
|
303
|
+
expect(assistantMessage.content).toBeDefined();
|
|
304
|
+
expect(assistantMessage.content.length).toBeGreaterThan(0);
|
|
305
|
+
|
|
306
|
+
// Message should have accumulated text from all deltas
|
|
307
|
+
const textContent = assistantMessage.content.find(
|
|
308
|
+
(c: any) => c.kind === "text"
|
|
309
|
+
);
|
|
310
|
+
expect(textContent).toBeDefined();
|
|
311
|
+
expect(textContent.text).toBeDefined();
|
|
312
|
+
expect(textContent.text.length).toBeGreaterThan(0);
|
|
313
|
+
|
|
314
|
+
// Verify accumulated text matches concatenated deltas
|
|
315
|
+
const accumulatedFromDeltas = textDeltas
|
|
316
|
+
.map((d: any) => d.text)
|
|
317
|
+
.join("");
|
|
318
|
+
expect(textContent.text).toBe(accumulatedFromDeltas);
|
|
319
|
+
|
|
320
|
+
// Should have finish event
|
|
321
|
+
const finishEvents = events.filter((e) => e.kind === "finish");
|
|
322
|
+
expect(finishEvents.length).toBe(1);
|
|
323
|
+
});
|
|
260
324
|
});
|
|
261
325
|
|
|
262
326
|
describe("tools", () => {
|
|
@@ -402,6 +466,103 @@ describe.skipIf(SKIP_INTEGRATION_TESTS)(
|
|
|
402
466
|
);
|
|
403
467
|
expect(toolCalls.length).toBeGreaterThan(0);
|
|
404
468
|
});
|
|
469
|
+
|
|
470
|
+
it("should handle multi-turn conversation with tool results", async () => {
|
|
471
|
+
// First turn: get tool calls from the model
|
|
472
|
+
const firstResponse = await gpt4omini.generate({
|
|
473
|
+
input: [
|
|
474
|
+
{
|
|
475
|
+
kind: "message",
|
|
476
|
+
role: "user",
|
|
477
|
+
id: "msg-1",
|
|
478
|
+
content: [{ kind: "text", text: "What is 25 + 17?" }],
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
tools: [
|
|
482
|
+
{
|
|
483
|
+
kind: "function",
|
|
484
|
+
name: "calculate",
|
|
485
|
+
description: "Perform a mathematical calculation",
|
|
486
|
+
parameters: {
|
|
487
|
+
type: "object",
|
|
488
|
+
properties: {
|
|
489
|
+
expression: {
|
|
490
|
+
type: "string",
|
|
491
|
+
description: "The mathematical expression to evaluate",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
required: ["expression"],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
settings: {
|
|
499
|
+
maxTokens: 200,
|
|
500
|
+
temperature: 0,
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
expect(firstResponse.content).toBeDefined();
|
|
505
|
+
|
|
506
|
+
// Extract tool calls
|
|
507
|
+
const toolCalls = firstResponse.content.filter(
|
|
508
|
+
(item) => item.kind === "tool-call",
|
|
509
|
+
);
|
|
510
|
+
expect(toolCalls.length).toBeGreaterThan(0);
|
|
511
|
+
|
|
512
|
+
const toolCall = toolCalls[0] as any;
|
|
513
|
+
expect(toolCall.callId).toBeDefined();
|
|
514
|
+
expect(toolCall.toolId).toBe("calculate");
|
|
515
|
+
|
|
516
|
+
// Second turn: send tool results back to the model
|
|
517
|
+
const secondResponse = await gpt4omini.generate({
|
|
518
|
+
input: [
|
|
519
|
+
{
|
|
520
|
+
kind: "message",
|
|
521
|
+
role: "user",
|
|
522
|
+
id: "msg-1",
|
|
523
|
+
content: [{ kind: "text", text: "What is 25 + 17?" }],
|
|
524
|
+
},
|
|
525
|
+
...firstResponse.content,
|
|
526
|
+
{
|
|
527
|
+
kind: "tool-result",
|
|
528
|
+
callId: toolCall.callId,
|
|
529
|
+
toolId: toolCall.toolId,
|
|
530
|
+
state: "completed",
|
|
531
|
+
result: { answer: 42 },
|
|
532
|
+
error: null,
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
tools: [
|
|
536
|
+
{
|
|
537
|
+
kind: "function",
|
|
538
|
+
name: "calculate",
|
|
539
|
+
description: "Perform a mathematical calculation",
|
|
540
|
+
parameters: {
|
|
541
|
+
type: "object",
|
|
542
|
+
properties: {
|
|
543
|
+
expression: {
|
|
544
|
+
type: "string",
|
|
545
|
+
description: "The mathematical expression to evaluate",
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
required: ["expression"],
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
settings: {
|
|
553
|
+
maxTokens: 200,
|
|
554
|
+
temperature: 0,
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
expect(secondResponse.content).toBeDefined();
|
|
559
|
+
|
|
560
|
+
// Should have an assistant message with the final answer
|
|
561
|
+
const messages = secondResponse.content.filter(
|
|
562
|
+
(item) => item.kind === "message" && item.role === "assistant",
|
|
563
|
+
);
|
|
564
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
565
|
+
});
|
|
405
566
|
});
|
|
406
567
|
|
|
407
568
|
describe("validation", () => {
|