@kernl-sdk/ai 0.1.1

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 (81) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +10 -0
  3. package/LICENSE +201 -0
  4. package/dist/__tests__/integration.test.d.ts +2 -0
  5. package/dist/__tests__/integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/integration.test.js +388 -0
  7. package/dist/convert/__tests__/message.test.d.ts +2 -0
  8. package/dist/convert/__tests__/message.test.d.ts.map +1 -0
  9. package/dist/convert/__tests__/message.test.js +300 -0
  10. package/dist/convert/__tests__/response.test.d.ts +2 -0
  11. package/dist/convert/__tests__/response.test.d.ts.map +1 -0
  12. package/dist/convert/__tests__/response.test.js +49 -0
  13. package/dist/convert/__tests__/settings.test.d.ts +2 -0
  14. package/dist/convert/__tests__/settings.test.d.ts.map +1 -0
  15. package/dist/convert/__tests__/settings.test.js +144 -0
  16. package/dist/convert/__tests__/stream.test.d.ts +2 -0
  17. package/dist/convert/__tests__/stream.test.d.ts.map +1 -0
  18. package/dist/convert/__tests__/stream.test.js +389 -0
  19. package/dist/convert/__tests__/tools.test.d.ts +2 -0
  20. package/dist/convert/__tests__/tools.test.d.ts.map +1 -0
  21. package/dist/convert/__tests__/tools.test.js +152 -0
  22. package/dist/convert/message.d.ts +4 -0
  23. package/dist/convert/message.d.ts.map +1 -0
  24. package/dist/convert/message.js +122 -0
  25. package/dist/convert/messages.d.ts +4 -0
  26. package/dist/convert/messages.d.ts.map +1 -0
  27. package/dist/convert/messages.js +130 -0
  28. package/dist/convert/response.d.ts +15 -0
  29. package/dist/convert/response.d.ts.map +1 -0
  30. package/dist/convert/response.js +105 -0
  31. package/dist/convert/settings.d.ts +16 -0
  32. package/dist/convert/settings.d.ts.map +1 -0
  33. package/dist/convert/settings.js +36 -0
  34. package/dist/convert/stream.d.ts +11 -0
  35. package/dist/convert/stream.d.ts.map +1 -0
  36. package/dist/convert/stream.js +154 -0
  37. package/dist/convert/tools.d.ts +5 -0
  38. package/dist/convert/tools.d.ts.map +1 -0
  39. package/dist/convert/tools.js +42 -0
  40. package/dist/error.d.ts +8 -0
  41. package/dist/error.d.ts.map +1 -0
  42. package/dist/error.js +15 -0
  43. package/dist/index.d.ts +20 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +20 -0
  46. package/dist/language-model.d.ts +21 -0
  47. package/dist/language-model.d.ts.map +1 -0
  48. package/dist/language-model.js +60 -0
  49. package/dist/providers/anthropic.d.ts +14 -0
  50. package/dist/providers/anthropic.d.ts.map +1 -0
  51. package/dist/providers/anthropic.js +17 -0
  52. package/dist/providers/google.d.ts +14 -0
  53. package/dist/providers/google.d.ts.map +1 -0
  54. package/dist/providers/google.js +17 -0
  55. package/dist/providers/openai.d.ts +14 -0
  56. package/dist/providers/openai.d.ts.map +1 -0
  57. package/dist/providers/openai.js +17 -0
  58. package/dist/types.d.ts +1 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +1 -0
  61. package/package.json +79 -0
  62. package/src/__tests__/integration.test.ts +447 -0
  63. package/src/convert/__tests__/message.test.ts +336 -0
  64. package/src/convert/__tests__/response.test.ts +63 -0
  65. package/src/convert/__tests__/settings.test.ts +188 -0
  66. package/src/convert/__tests__/stream.test.ts +460 -0
  67. package/src/convert/__tests__/tools.test.ts +179 -0
  68. package/src/convert/message.ts +150 -0
  69. package/src/convert/response.ts +144 -0
  70. package/src/convert/settings.ts +62 -0
  71. package/src/convert/stream.ts +181 -0
  72. package/src/convert/tools.ts +59 -0
  73. package/src/error.ts +16 -0
  74. package/src/index.ts +22 -0
  75. package/src/language-model.ts +77 -0
  76. package/src/providers/anthropic.ts +18 -0
  77. package/src/providers/google.ts +18 -0
  78. package/src/providers/openai.ts +18 -0
  79. package/src/types.ts +0 -0
  80. package/tsconfig.json +13 -0
  81. package/vitest.config.ts +14 -0
@@ -0,0 +1,336 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import type { LanguageModelV3Message } from "@ai-sdk/provider";
3
+
4
+ import { MESSAGE } from "../message";
5
+
6
+ describe("MESSAGE codec", () => {
7
+ describe("encode - system messages", () => {
8
+ it("should encode system message with single text part", () => {
9
+ const result = MESSAGE.encode({
10
+ kind: "message",
11
+ role: "system",
12
+ id: "msg-1",
13
+ content: [{ kind: "text", text: "You are a helpful assistant." }],
14
+ });
15
+
16
+ expect(result).toEqual({
17
+ role: "system",
18
+ content: "You are a helpful assistant.",
19
+ providerOptions: undefined,
20
+ });
21
+ });
22
+
23
+ it("should encode system message with multiple text parts", () => {
24
+ const result = MESSAGE.encode({
25
+ kind: "message",
26
+ role: "system",
27
+ id: "msg-1",
28
+ content: [
29
+ { kind: "text", text: "You are helpful." },
30
+ { kind: "text", text: "You are concise." },
31
+ ],
32
+ });
33
+
34
+ expect(result).toEqual({
35
+ role: "system",
36
+ content: "You are helpful.\nYou are concise.",
37
+ providerOptions: undefined,
38
+ });
39
+ });
40
+
41
+ it("should filter out non-text parts from system messages", () => {
42
+ const result = MESSAGE.encode({
43
+ kind: "message",
44
+ role: "system",
45
+ id: "msg-1",
46
+ content: [
47
+ { kind: "text", text: "System prompt" },
48
+ { kind: "file", data: "base64data", mimeType: "image/png" },
49
+ ],
50
+ });
51
+
52
+ expect(result).toEqual({
53
+ role: "system",
54
+ content: "System prompt",
55
+ providerOptions: undefined,
56
+ });
57
+ });
58
+
59
+ it("should include providerMetadata when present", () => {
60
+ const result = MESSAGE.encode({
61
+ kind: "message",
62
+ role: "system",
63
+ id: "msg-1",
64
+ content: [{ kind: "text", text: "System" }],
65
+ providerMetadata: { openai: { custom: "metadata" } },
66
+ });
67
+
68
+ expect(result).toEqual({
69
+ role: "system",
70
+ content: "System",
71
+ providerOptions: { openai: { custom: "metadata" } },
72
+ });
73
+ });
74
+ });
75
+
76
+ describe("encode - user messages", () => {
77
+ it("should encode user message with text part", () => {
78
+ const result = MESSAGE.encode({
79
+ kind: "message",
80
+ role: "user",
81
+ id: "msg-1",
82
+ content: [{ kind: "text", text: "Hello!" }],
83
+ });
84
+
85
+ expect(result).toEqual({
86
+ role: "user",
87
+ content: [
88
+ {
89
+ type: "text",
90
+ text: "Hello!",
91
+ providerOptions: undefined,
92
+ },
93
+ ],
94
+ providerOptions: undefined,
95
+ });
96
+ });
97
+
98
+ it("should encode user message with file part", () => {
99
+ const result = MESSAGE.encode({
100
+ kind: "message",
101
+ role: "user",
102
+ id: "msg-1",
103
+ content: [
104
+ {
105
+ kind: "file",
106
+ data: "base64imagedata",
107
+ mimeType: "image/png",
108
+ filename: "screenshot.png",
109
+ },
110
+ ],
111
+ });
112
+
113
+ expect(result).toEqual({
114
+ role: "user",
115
+ content: [
116
+ {
117
+ type: "file",
118
+ filename: "screenshot.png",
119
+ data: "base64imagedata",
120
+ mediaType: "image/png",
121
+ providerOptions: undefined,
122
+ },
123
+ ],
124
+ providerOptions: undefined,
125
+ });
126
+ });
127
+
128
+ it("should encode user message with mixed text and file parts", () => {
129
+ const result = MESSAGE.encode({
130
+ kind: "message",
131
+ role: "user",
132
+ id: "msg-1",
133
+ content: [
134
+ { kind: "text", text: "Look at this:" },
135
+ {
136
+ kind: "file",
137
+ data: "base64data",
138
+ mimeType: "image/jpeg" as const,
139
+ },
140
+ ],
141
+ });
142
+
143
+ expect(result.role).toBe("user");
144
+ expect(result.content).toHaveLength(2);
145
+ expect(result.content[0]).toEqual({
146
+ type: "text",
147
+ text: "Look at this:",
148
+ providerOptions: undefined,
149
+ });
150
+ expect(result.content[1]).toEqual({
151
+ type: "file",
152
+ filename: undefined,
153
+ data: "base64data",
154
+ mediaType: "image/jpeg",
155
+ providerOptions: undefined,
156
+ });
157
+ });
158
+ });
159
+
160
+ describe("encode - assistant messages", () => {
161
+ it("should encode assistant message with text part", () => {
162
+ const result = MESSAGE.encode({
163
+ kind: "message",
164
+ role: "assistant",
165
+ id: "msg-1",
166
+ content: [{ kind: "text", text: "I can help with that." }],
167
+ });
168
+
169
+ expect(result).toEqual({
170
+ role: "assistant",
171
+ content: [
172
+ {
173
+ type: "text",
174
+ text: "I can help with that.",
175
+ providerOptions: undefined,
176
+ },
177
+ ],
178
+ providerOptions: undefined,
179
+ });
180
+ });
181
+
182
+ it("should encode assistant message with file part", () => {
183
+ const result = MESSAGE.encode({
184
+ kind: "message",
185
+ role: "assistant",
186
+ id: "msg-1",
187
+ content: [
188
+ {
189
+ kind: "file",
190
+ data: "chartdata",
191
+ mimeType: "image/svg+xml" as const,
192
+ },
193
+ ],
194
+ });
195
+
196
+ expect(result).toEqual({
197
+ role: "assistant",
198
+ content: [
199
+ {
200
+ type: "file",
201
+ filename: undefined,
202
+ data: "chartdata",
203
+ mediaType: "image/svg+xml",
204
+ providerOptions: undefined,
205
+ },
206
+ ],
207
+ providerOptions: undefined,
208
+ });
209
+ });
210
+ });
211
+
212
+ describe("encode - tool-call items", () => {
213
+ it("should encode tool-call item", () => {
214
+ const result = MESSAGE.encode({
215
+ kind: "tool-call",
216
+ callId: "call-123",
217
+ toolId: "get_weather",
218
+ state: "completed",
219
+ arguments: JSON.stringify({ city: "SF" }),
220
+ });
221
+
222
+ expect(result).toEqual({
223
+ role: "assistant",
224
+ content: [
225
+ {
226
+ type: "tool-call",
227
+ toolCallId: "call-123",
228
+ toolName: "get_weather",
229
+ input: { city: "SF" },
230
+ providerOptions: undefined,
231
+ },
232
+ ],
233
+ });
234
+ });
235
+
236
+ it("should include providerMetadata for tool-call", () => {
237
+ const result = MESSAGE.encode({
238
+ kind: "tool-call",
239
+ callId: "call-123",
240
+ toolId: "get_weather",
241
+ state: "completed",
242
+ arguments: JSON.stringify({ city: "SF" }),
243
+ providerMetadata: { anthropic: { executionTime: 150 } },
244
+ });
245
+
246
+ expect(result.content[0]).toMatchObject({
247
+ type: "tool-call",
248
+ providerOptions: { anthropic: { executionTime: 150 } },
249
+ });
250
+ });
251
+ });
252
+
253
+ describe("encode - tool-result items", () => {
254
+ it("should encode tool-result item", () => {
255
+ const result = MESSAGE.encode({
256
+ kind: "tool-result",
257
+ callId: "call-123",
258
+ toolId: "get_weather",
259
+ state: "completed",
260
+ result: { temp: 72, conditions: "sunny" },
261
+ error: null,
262
+ });
263
+
264
+ expect(result).toEqual({
265
+ role: "tool",
266
+ content: [
267
+ {
268
+ type: "tool-result",
269
+ toolCallId: "call-123",
270
+ toolName: "get_weather",
271
+ output: {
272
+ type: "json",
273
+ value: { temp: 72, conditions: "sunny" },
274
+ },
275
+ providerOptions: undefined,
276
+ },
277
+ ],
278
+ });
279
+ });
280
+ });
281
+
282
+ describe("encode - reasoning items", () => {
283
+ it("should encode reasoning item with text", () => {
284
+ const result = MESSAGE.encode({
285
+ kind: "reasoning",
286
+ text: "Let me think about this step by step...",
287
+ });
288
+
289
+ expect(result).toEqual({
290
+ role: "assistant",
291
+ content: [
292
+ {
293
+ type: "reasoning",
294
+ text: "Let me think about this step by step...",
295
+ providerOptions: undefined,
296
+ },
297
+ ],
298
+ });
299
+ });
300
+
301
+ it("should encode reasoning item without text field", () => {
302
+ const result = MESSAGE.encode({
303
+ kind: "reasoning",
304
+ } as any);
305
+
306
+ expect(result).toEqual({
307
+ role: "assistant",
308
+ content: [
309
+ {
310
+ type: "reasoning",
311
+ text: "",
312
+ providerOptions: undefined,
313
+ },
314
+ ],
315
+ });
316
+ });
317
+ });
318
+
319
+ describe("encode - unsupported items", () => {
320
+ it("should throw error for unsupported item kind", () => {
321
+ expect(() =>
322
+ MESSAGE.encode({
323
+ kind: "unknown-kind",
324
+ } as any),
325
+ ).toThrow("Unsupported LanguageModelItem kind: unknown-kind");
326
+ });
327
+ });
328
+
329
+ describe("decode", () => {
330
+ it("should throw unimplemented error", () => {
331
+ expect(() => MESSAGE.decode({} as LanguageModelV3Message)).toThrow(
332
+ "codec:unimplemented",
333
+ );
334
+ });
335
+ });
336
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import type { LanguageModelV3CallWarning } from "@ai-sdk/provider";
3
+
4
+ import { WARNING } from "../response";
5
+
6
+ describe("WARNING codec", () => {
7
+ describe("decode", () => {
8
+ it("should decode unsupported-setting warning", () => {
9
+ const aiWarning: LanguageModelV3CallWarning = {
10
+ type: "unsupported-setting",
11
+ setting: "someUnsupportedSetting",
12
+ details: "This setting is not supported by the provider",
13
+ };
14
+
15
+ const result = WARNING.decode(aiWarning);
16
+
17
+ expect(result).toEqual({
18
+ type: "unsupported-setting",
19
+ setting: "someUnsupportedSetting",
20
+ details: "This setting is not supported by the provider",
21
+ });
22
+ });
23
+
24
+ it("should decode other warning", () => {
25
+ const aiWarning: LanguageModelV3CallWarning = {
26
+ type: "other",
27
+ message: "Some custom warning message",
28
+ };
29
+
30
+ const result = WARNING.decode(aiWarning);
31
+
32
+ expect(result).toEqual({
33
+ type: "other",
34
+ message: "Some custom warning message",
35
+ });
36
+ });
37
+
38
+ it("should handle unknown warning type", () => {
39
+ const aiWarning = {
40
+ type: "unknown-type",
41
+ someField: "value",
42
+ } as any;
43
+
44
+ const result = WARNING.decode(aiWarning);
45
+
46
+ expect(result).toEqual({
47
+ type: "other",
48
+ message: "Unknown warning type",
49
+ });
50
+ });
51
+ });
52
+
53
+ describe("encode", () => {
54
+ it("should throw unimplemented error", () => {
55
+ expect(() =>
56
+ WARNING.encode({
57
+ type: "other",
58
+ message: "test",
59
+ }),
60
+ ).toThrow("codec:unimplemented");
61
+ });
62
+ });
63
+ });
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import type { LanguageModelRequestSettings } from "@kernl-sdk/protocol";
3
+
4
+ import { MODEL_SETTINGS } from "../settings";
5
+
6
+ describe("MODEL_SETTINGS codec", () => {
7
+ describe("encode", () => {
8
+ it("should encode temperature setting", () => {
9
+ const settings: LanguageModelRequestSettings = {
10
+ temperature: 0.7,
11
+ };
12
+
13
+ const result = MODEL_SETTINGS.encode(settings);
14
+
15
+ expect(result).toEqual({
16
+ temperature: 0.7,
17
+ });
18
+ });
19
+
20
+ it("should encode topP setting", () => {
21
+ const settings: LanguageModelRequestSettings = {
22
+ topP: 0.9,
23
+ };
24
+
25
+ const result = MODEL_SETTINGS.encode(settings);
26
+
27
+ expect(result).toEqual({
28
+ topP: 0.9,
29
+ });
30
+ });
31
+
32
+ it("should encode maxTokens setting", () => {
33
+ const settings: LanguageModelRequestSettings = {
34
+ maxTokens: 1000,
35
+ };
36
+
37
+ const result = MODEL_SETTINGS.encode(settings);
38
+
39
+ expect(result).toEqual({
40
+ maxOutputTokens: 1000,
41
+ });
42
+ });
43
+
44
+ it("should encode frequencyPenalty setting", () => {
45
+ const settings: LanguageModelRequestSettings = {
46
+ frequencyPenalty: 0.5,
47
+ };
48
+
49
+ const result = MODEL_SETTINGS.encode(settings);
50
+
51
+ expect(result).toEqual({
52
+ frequencyPenalty: 0.5,
53
+ });
54
+ });
55
+
56
+ it("should encode presencePenalty setting", () => {
57
+ const settings: LanguageModelRequestSettings = {
58
+ presencePenalty: 0.3,
59
+ };
60
+
61
+ const result = MODEL_SETTINGS.encode(settings);
62
+
63
+ expect(result).toEqual({
64
+ presencePenalty: 0.3,
65
+ });
66
+ });
67
+
68
+ it("should encode toolChoice 'auto'", () => {
69
+ const settings: LanguageModelRequestSettings = {
70
+ toolChoice: { kind: "auto" },
71
+ };
72
+
73
+ const result = MODEL_SETTINGS.encode(settings);
74
+
75
+ expect(result).toEqual({
76
+ toolChoice: { type: "auto" },
77
+ });
78
+ });
79
+
80
+ it("should encode toolChoice 'none'", () => {
81
+ const settings: LanguageModelRequestSettings = {
82
+ toolChoice: { kind: "none" },
83
+ };
84
+
85
+ const result = MODEL_SETTINGS.encode(settings);
86
+
87
+ expect(result).toEqual({
88
+ toolChoice: { type: "none" },
89
+ });
90
+ });
91
+
92
+ it("should encode toolChoice 'required'", () => {
93
+ const settings: LanguageModelRequestSettings = {
94
+ toolChoice: { kind: "required" },
95
+ };
96
+
97
+ const result = MODEL_SETTINGS.encode(settings);
98
+
99
+ expect(result).toEqual({
100
+ toolChoice: { type: "required" },
101
+ });
102
+ });
103
+
104
+ it("should encode toolChoice with specific tool", () => {
105
+ const settings: LanguageModelRequestSettings = {
106
+ toolChoice: { kind: "tool", toolId: "get_weather" },
107
+ };
108
+
109
+ const result = MODEL_SETTINGS.encode(settings);
110
+
111
+ expect(result).toEqual({
112
+ toolChoice: { type: "tool", toolName: "get_weather" },
113
+ });
114
+ });
115
+
116
+ it("should encode providerOptions", () => {
117
+ const settings: LanguageModelRequestSettings = {
118
+ providerOptions: {
119
+ anthropic: {
120
+ cacheControl: { type: "ephemeral" },
121
+ },
122
+ },
123
+ };
124
+
125
+ const result = MODEL_SETTINGS.encode(settings);
126
+
127
+ expect(result).toEqual({
128
+ providerOptions: {
129
+ anthropic: {
130
+ cacheControl: { type: "ephemeral" },
131
+ },
132
+ },
133
+ });
134
+ });
135
+
136
+ it("should encode multiple settings together", () => {
137
+ const settings: LanguageModelRequestSettings = {
138
+ temperature: 0.8,
139
+ maxTokens: 2000,
140
+ topP: 0.95,
141
+ frequencyPenalty: 0.2,
142
+ presencePenalty: 0.1,
143
+ toolChoice: { kind: "auto" },
144
+ };
145
+
146
+ const result = MODEL_SETTINGS.encode(settings);
147
+
148
+ expect(result).toEqual({
149
+ temperature: 0.8,
150
+ maxOutputTokens: 2000,
151
+ topP: 0.95,
152
+ frequencyPenalty: 0.2,
153
+ presencePenalty: 0.1,
154
+ toolChoice: { type: "auto" },
155
+ });
156
+ });
157
+
158
+ it("should handle empty settings", () => {
159
+ const settings: LanguageModelRequestSettings = {};
160
+
161
+ const result = MODEL_SETTINGS.encode(settings);
162
+
163
+ expect(result).toEqual({});
164
+ });
165
+
166
+ it("should only include defined settings", () => {
167
+ const settings: LanguageModelRequestSettings = {
168
+ temperature: 0.5,
169
+ maxTokens: undefined,
170
+ };
171
+
172
+ const result = MODEL_SETTINGS.encode(settings);
173
+
174
+ expect(result).toEqual({
175
+ temperature: 0.5,
176
+ });
177
+ expect(result).not.toHaveProperty("maxTokens");
178
+ });
179
+ });
180
+
181
+ describe("decode", () => {
182
+ it("should throw unimplemented error", () => {
183
+ expect(() => MODEL_SETTINGS.decode({} as any)).toThrow(
184
+ "codec:unimplemented",
185
+ );
186
+ });
187
+ });
188
+ });