@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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @kernl-sdk/ai - AI SDK adapter for Kernl
2
+ * @kernl-sdk/ai - AI SDK adapter for kernl
3
3
  *
4
4
  * Universal provider support via Vercel AI SDK v5.
5
5
  *
@@ -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;AAS7B;;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;CAkB3C"}
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"}
@@ -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
- yield* convertStream(stream.stream);
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,6 +1,6 @@
1
1
  import { AISDKLanguageModel } from "../language-model";
2
2
  /**
3
- * Create a Kernl-compatible Anthropic language model.
3
+ * Create a kernl-compatible Anthropic language model.
4
4
  *
5
5
  * @example
6
6
  * ```ts
@@ -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 Kernl-compatible Anthropic language model.
4
+ * Create a kernl-compatible Anthropic language model.
5
5
  *
6
6
  * @example
7
7
  * ```ts
@@ -1,6 +1,6 @@
1
1
  import { AISDKLanguageModel } from "../language-model";
2
2
  /**
3
- * Create a Kernl-compatible Google Generative AI language model.
3
+ * Create a kernl-compatible Google Generative AI language model.
4
4
  *
5
5
  * @example
6
6
  * ```ts
@@ -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 Kernl-compatible Google Generative AI language model.
4
+ * Create a kernl-compatible Google Generative AI language model.
5
5
  *
6
6
  * @example
7
7
  * ```ts
@@ -1,6 +1,6 @@
1
1
  import { AISDKLanguageModel } from "../language-model";
2
2
  /**
3
- * Create a Kernl-compatible OpenAI language model.
3
+ * Create a kernl-compatible OpenAI language model.
4
4
  *
5
5
  * @example
6
6
  * ```ts
@@ -1,7 +1,7 @@
1
1
  import { openai as createOpenAIModel } from "@ai-sdk/openai";
2
2
  import { AISDKLanguageModel } from "../language-model";
3
3
  /**
4
- * Create a Kernl-compatible OpenAI language model.
4
+ * Create a kernl-compatible OpenAI language model.
5
5
  *
6
6
  * @example
7
7
  * ```ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kernl-sdk/ai",
3
- "version": "0.1.1",
4
- "description": "AI SDK adapter for Kernl - Universal provider support via Vercel AI SDK",
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.1.1"
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.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", () => {