@bedrockio/ai 0.2.1 → 0.4.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.
Files changed (63) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +59 -17
  4. package/__mocks__/@anthropic-ai/sdk.js +16 -22
  5. package/__mocks__/@google/generative-ai.js +1 -1
  6. package/__mocks__/openai.js +33 -28
  7. package/dist/cjs/BaseClient.js +242 -126
  8. package/dist/cjs/anthropic.js +115 -93
  9. package/dist/cjs/google.js +74 -86
  10. package/dist/cjs/index.js +24 -25
  11. package/dist/cjs/openai.js +114 -69
  12. package/dist/cjs/package.json +1 -0
  13. package/dist/cjs/utils/code.js +11 -0
  14. package/dist/cjs/utils/json.js +53 -0
  15. package/dist/cjs/utils/templates.js +83 -0
  16. package/dist/cjs/xai.js +14 -0
  17. package/dist/esm/BaseClient.js +243 -0
  18. package/dist/esm/anthropic.js +116 -0
  19. package/dist/esm/google.js +75 -0
  20. package/dist/esm/index.js +25 -0
  21. package/dist/esm/openai.js +113 -0
  22. package/dist/esm/utils/code.js +8 -0
  23. package/dist/esm/utils/json.js +50 -0
  24. package/dist/esm/utils/templates.js +76 -0
  25. package/dist/esm/xai.js +10 -0
  26. package/eslint.config.js +2 -0
  27. package/package.json +18 -17
  28. package/src/BaseClient.js +239 -89
  29. package/src/anthropic.js +96 -56
  30. package/src/google.js +6 -12
  31. package/src/index.js +20 -16
  32. package/src/openai.js +97 -31
  33. package/src/utils/code.js +9 -0
  34. package/src/utils/json.js +58 -0
  35. package/src/utils/templates.js +87 -0
  36. package/src/xai.js +12 -0
  37. package/tsconfig.cjs.json +8 -0
  38. package/tsconfig.esm.json +8 -0
  39. package/tsconfig.types.json +9 -0
  40. package/types/BaseClient.d.ts +68 -26
  41. package/types/BaseClient.d.ts.map +1 -1
  42. package/types/anthropic.d.ts +26 -2
  43. package/types/anthropic.d.ts.map +1 -1
  44. package/types/google.d.ts.map +1 -1
  45. package/types/index.d.ts +4 -3
  46. package/types/index.d.ts.map +1 -1
  47. package/types/openai.d.ts +45 -2
  48. package/types/openai.d.ts.map +1 -1
  49. package/types/util.d.ts +1 -1
  50. package/types/util.d.ts.map +1 -1
  51. package/types/utils/code.d.ts +2 -0
  52. package/types/utils/code.d.ts.map +1 -0
  53. package/types/utils/json.d.ts +2 -0
  54. package/types/utils/json.d.ts.map +1 -0
  55. package/types/utils/templates.d.ts +3 -0
  56. package/types/utils/templates.d.ts.map +1 -0
  57. package/types/utils.d.ts +4 -0
  58. package/types/utils.d.ts.map +1 -0
  59. package/types/xai.d.ts +4 -0
  60. package/types/xai.d.ts.map +1 -0
  61. package/vitest.config.js +10 -0
  62. package/dist/cjs/util.js +0 -47
  63. package/src/util.js +0 -42
@@ -1,130 +1,246 @@
1
1
  "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _mustache = _interopRequireDefault(require("mustache"));
8
- var _util = require("./util.js");
9
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
- const MESSAGES_REG = /(?:^|\n)-{3,}\s*(\w+)\s*-{3,}(.*?)(?=\n-{3,}|$)/gs;
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const code_js_1 = require("./utils/code.js");
4
+ const json_js_1 = require("./utils/json.js");
5
+ const templates_js_1 = require("./utils/templates.js");
11
6
  class BaseClient {
12
- constructor(options) {
13
- this.options = options;
14
- this.templates = null;
15
- }
16
-
17
- /**
18
- * Interpolates vars into the provided template and
19
- * runs the chat completion. The "output" option may
20
- * be omitted and will default to `"text"`.
21
- * {@link https://github.com/bedrockio/ai?tab=readme-ov-file#bedrockioai Documentation}
22
- *
23
- * @param {object} options
24
- * @param {string} options.model - The model to use.
25
- * @param {"raw" | "text" | "json" | "messages"} [options.output] - The output to use.
26
- * @param {Object.<string, any>} [options.other] - Additional props
27
- * will be interpolated in the template.
28
- */
29
- async prompt(options) {
30
- options = {
31
- ...this.options,
32
- ...options
33
- };
34
- const messages = await this.getMessages(options);
35
- return await this.getCompletion({
36
- ...options,
37
- messages
38
- });
39
- }
40
-
41
- /**
42
- * Streams the prompt response.
43
- * @returns {AsyncIterator}
44
- */
45
- async *stream(options) {
46
- const stream = await this.getStream(options);
47
- let started = false;
48
-
49
- // @ts-ignore
50
- for await (const chunk of stream) {
51
- const resolved = this.getStreamedChunk(chunk, started);
52
- started = true;
53
-
54
- // @ts-ignore
55
- if (resolved) {
56
- yield resolved;
57
- }
58
- }
59
- }
60
- async getMessages(options) {
61
- const template = await this.resolveTemplate(options);
62
- const raw = _mustache.default.render(template, transformParams(options));
63
- const messages = [];
64
- for (let match of raw.matchAll(MESSAGES_REG)) {
65
- const [, role, content] = match;
66
- messages.push({
67
- role: role.toLowerCase(),
68
- content: content.trim()
69
- });
70
- }
71
- if (!messages.length) {
72
- messages.push({
73
- role: 'user',
74
- content: raw.trim()
75
- });
76
- }
77
- return messages;
78
- }
79
- async loadTemplates() {
80
- const {
81
- templates
82
- } = this.options;
83
- this.templates ||= await (0, _util.loadTemplates)(templates);
84
- }
85
- async resolveTemplate(options) {
86
- await this.loadTemplates();
87
- let {
88
- file,
89
- template
90
- } = options;
91
- if (!template && file) {
92
- template = this.templates[file];
93
- }
94
- if (!template) {
95
- throw new Error('No template provided.');
96
- }
97
- return template;
98
- }
99
- async getStream(options) {
100
- return await this.prompt({
101
- ...options,
102
- output: 'raw',
103
- stream: true
104
- });
105
- }
106
- getCompletion(options) {
107
- void options;
108
- new Error('Method not implemented.');
109
- }
110
- getStreamedChunk(chunk, started) {
111
- void chunk;
112
- void started;
113
- new Error('Method not implemented.');
114
- }
7
+ constructor(options) {
8
+ this.options = {
9
+ // @ts-ignore
10
+ model: this.constructor.DEFAULT_MODEL,
11
+ ...options,
12
+ };
13
+ this.templates = null;
14
+ }
15
+ // Public
16
+ /**
17
+ * Interpolates vars into the provided template as instructions and runs the
18
+ * prompt.
19
+ *
20
+ * @param {PromptOptions} options
21
+ */
22
+ async prompt(options) {
23
+ options = await this.normalizeOptions(options);
24
+ const { input, output, stream, schema } = options;
25
+ const response = await this.runPrompt(options);
26
+ if (!stream) {
27
+ this.debug('Response:', response);
28
+ }
29
+ if (output === 'raw') {
30
+ return response;
31
+ }
32
+ let result;
33
+ if (schema) {
34
+ result = this.getStructuredResponse(response);
35
+ // @ts-ignore
36
+ if (options.hasWrappedSchema) {
37
+ result = result.items;
38
+ }
39
+ }
40
+ else if (output === 'json') {
41
+ result = JSON.parse((0, code_js_1.parseCode)(this.getTextResponse(response)));
42
+ }
43
+ else {
44
+ result = (0, code_js_1.parseCode)(this.getTextResponse(response));
45
+ }
46
+ if (output === 'messages') {
47
+ return {
48
+ result,
49
+ ...this.getMessagesResponse(input, response),
50
+ };
51
+ }
52
+ else {
53
+ return result;
54
+ }
55
+ }
56
+ /**
57
+ * Streams the prompt response.
58
+ *
59
+ * @param {PromptOptions & StreamOptions} options
60
+ * @returns {AsyncIterator}
61
+ */
62
+ async *stream(options) {
63
+ options = await this.normalizeOptions(options);
64
+ const extractor = this.getMessageExtractor(options);
65
+ try {
66
+ const stream = await this.runStream(options);
67
+ // @ts-ignore
68
+ for await (let event of stream) {
69
+ this.debug('Event:', event);
70
+ event = this.normalizeStreamEvent(event);
71
+ if (event) {
72
+ yield event;
73
+ }
74
+ const extractedMessages = extractor?.(event) || [];
75
+ for (let message of extractedMessages) {
76
+ const { key, delta, text, done } = message;
77
+ let extractEvent;
78
+ if (done) {
79
+ extractEvent = {
80
+ type: 'extract:done',
81
+ text,
82
+ key,
83
+ };
84
+ }
85
+ else {
86
+ extractEvent = {
87
+ type: 'extract:delta',
88
+ delta,
89
+ key,
90
+ };
91
+ }
92
+ this.debug('Extract:', extractEvent);
93
+ yield extractEvent;
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ const { message, code } = error;
99
+ yield {
100
+ type: 'error',
101
+ code,
102
+ message,
103
+ };
104
+ }
105
+ }
106
+ async buildTemplate(options) {
107
+ const template = await this.resolveTemplate(options);
108
+ return (0, templates_js_1.renderTemplate)(template, options);
109
+ }
110
+ // Protected
111
+ runPrompt(options) {
112
+ void options;
113
+ throw new Error('Method not implemented.');
114
+ }
115
+ runStream(options) {
116
+ void options;
117
+ throw new Error('Method not implemented.');
118
+ }
119
+ getTextResponse(response) {
120
+ void response;
121
+ throw new Error('Method not implemented.');
122
+ }
123
+ /**
124
+ * @returns {Object}
125
+ */
126
+ getStructuredResponse(response) {
127
+ void response;
128
+ throw new Error('Method not implemented.');
129
+ }
130
+ /**
131
+ * @returns {Object}
132
+ */
133
+ getMessagesResponse(input, response) {
134
+ void response;
135
+ throw new Error('Method not implemented.');
136
+ }
137
+ /**
138
+ * @returns {Object}
139
+ */
140
+ normalizeStreamEvent(event) {
141
+ void event;
142
+ throw new Error('Method not implemented.');
143
+ }
144
+ // Private
145
+ async normalizeOptions(options) {
146
+ options = {
147
+ input: '',
148
+ output: 'text',
149
+ ...this.options,
150
+ ...options,
151
+ };
152
+ options.input = this.normalizeInput(options);
153
+ options.schema = this.normalizeSchema(options);
154
+ options.instructions ||= await this.resolveInstructions(options);
155
+ return options;
156
+ }
157
+ normalizeInput(options) {
158
+ let { input = '', output } = options;
159
+ if (typeof input === 'string') {
160
+ if (output === 'json') {
161
+ input += '\nOutput only valid JSON.';
162
+ }
163
+ input = [
164
+ {
165
+ role: 'user',
166
+ content: input,
167
+ },
168
+ ];
169
+ }
170
+ return input;
171
+ }
172
+ normalizeSchema(options) {
173
+ let { schema } = options;
174
+ if (!schema) {
175
+ return;
176
+ }
177
+ // Convert to JSON schema.
178
+ schema = schema.toJSON?.() || schema;
179
+ if (schema?.type === 'array') {
180
+ schema = {
181
+ type: 'object',
182
+ properties: {
183
+ items: schema,
184
+ },
185
+ required: ['items'],
186
+ additionalProperties: false,
187
+ };
188
+ options.hasWrappedSchema = true;
189
+ }
190
+ return schema;
191
+ }
192
+ getMessageExtractor(options) {
193
+ const { extractMessages } = options;
194
+ if (!extractMessages) {
195
+ return;
196
+ }
197
+ const messageExtractor = (0, json_js_1.createMessageExtractor)([extractMessages]);
198
+ return (event) => {
199
+ if (event?.type === 'delta') {
200
+ return messageExtractor(event.delta);
201
+ }
202
+ };
203
+ }
204
+ debug(message, arg) {
205
+ if (this.options.debug) {
206
+ // TODO: replace with logger when opentelemetry is removed
207
+ // eslint-disable-next-line
208
+ console.debug(`${message}\n${JSON.stringify(arg, null, 2)}\n`);
209
+ }
210
+ }
211
+ async resolveInstructions(options) {
212
+ if (options.template) {
213
+ const template = await this.resolveTemplate(options);
214
+ return (0, templates_js_1.renderTemplate)(template, options);
215
+ }
216
+ }
217
+ async resolveTemplate(options) {
218
+ const { template } = options;
219
+ await this.loadTemplates();
220
+ return this.templates[template] || template;
221
+ }
222
+ async loadTemplates() {
223
+ const { templates } = this.options;
224
+ this.templates ||= await (0, templates_js_1.loadTemplates)(templates);
225
+ }
115
226
  }
116
227
  exports.default = BaseClient;
117
- function transformParams(params) {
118
- const result = {};
119
- for (let [key, value] of Object.entries(params)) {
120
- if (Array.isArray(value)) {
121
- value = value.map(el => {
122
- return `- ${el}`;
123
- }).join('\n');
124
- } else if (typeof value === 'object') {
125
- value = JSON.stringify(value, null, 2);
126
- }
127
- result[key] = value;
128
- }
129
- return result;
130
- }
228
+ /**
229
+ * @typedef {Object} PromptOptions
230
+ * @property {string|PromptMessage[]} input - Input to use.
231
+ * @property {string} [model] - The model to use.
232
+ * @property {boolean} stream - Stream response.
233
+ * @property {Object} [schema] - A JSON schema compatible object that defines the output shape.
234
+ * @property {"raw" | "text" | "json" | "messages"} [output] - The return value type.
235
+ * @property {Object} [params] - Params to be interpolated into the template.
236
+ * May also be passed as additional props to options.
237
+ */
238
+ /**
239
+ * @typedef {Object} StreamOptions
240
+ * @property {string} [extractMessages] - Key in JSON response to extract a message stream from.
241
+ */
242
+ /**
243
+ * @typedef {Object} PromptMessage
244
+ * @property {"system" | "user" | "assistant"} role
245
+ * @property {string} content
246
+ */
@@ -1,101 +1,123 @@
1
1
  "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AnthropicClient = void 0;
7
- var _sdk = _interopRequireDefault(require("@anthropic-ai/sdk"));
8
- var _BaseClient = _interopRequireDefault(require("./BaseClient.js"));
9
- var _util = require("./util.js");
10
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
- const MODELS_URL = 'https://docs.anthropic.com/en/docs/about-claude/models';
12
- const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
13
- class AnthropicClient extends _BaseClient.default {
14
- constructor(options) {
15
- super(options);
16
- this.client = new _sdk.default({
17
- ...options
18
- });
19
- }
20
-
21
- /**
22
- * Lists available models.
23
- * {@link https://docs.anthropic.com/en/docs/about-claude/models Documentation}
24
- */
25
- async models() {
26
- const {
27
- data
28
- } = await this.client.models.list();
29
- return data.map(o => o.id);
30
- }
31
- async getCompletion(options) {
32
- const {
33
- model = DEFAULT_MODEL,
34
- max_tokens = 2048,
35
- output = 'text',
36
- stream = false,
37
- messages
38
- } = options;
39
- const {
40
- client
41
- } = this;
42
- const {
43
- system,
44
- user
45
- } = splitMessages(messages);
46
- if (!model) {
47
- throw new Error(`No model specified. Available models are here: ${MODELS_URL}.`);
7
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
8
+ const BaseClient_js_1 = __importDefault(require("./BaseClient.js"));
9
+ const DEFAULT_TOKENS = 4096;
10
+ class AnthropicClient extends BaseClient_js_1.default {
11
+ static DEFAULT_MODEL = 'claude-sonnet-4-5';
12
+ constructor(options) {
13
+ super(options);
14
+ this.client = new sdk_1.default(options);
48
15
  }
49
- const response = await client.messages.create({
50
- max_tokens,
51
- messages: user,
52
- system,
53
- model,
54
- stream
55
- });
56
- if (output === 'raw') {
57
- return response;
16
+ /**
17
+ * Lists available models.
18
+ * {@link https://docs.anthropic.com/en/docs/about-claude/models Documentation}
19
+ */
20
+ async models() {
21
+ const { data } = await this.client.models.list();
22
+ return data.map((o) => o.id);
58
23
  }
59
-
60
- // @ts-ignore
61
- const message = response.content[0];
62
- return (0, _util.transformResponse)({
63
- ...options,
64
- messages,
65
- message
66
- });
67
- }
68
- getStreamedChunk(chunk) {
69
- // @ts-ignore
70
- let type;
71
- if (chunk.type === 'content_block_start') {
72
- type = 'start';
73
- } else if (chunk.type === 'content_block_delta') {
74
- type = 'chunk';
75
- } else if (chunk.type === 'message_stop') {
76
- type = 'stop';
24
+ async runPrompt(options) {
25
+ const { input, model, temperature, instructions, stream = false, tokens = DEFAULT_TOKENS, } = options;
26
+ // @ts-ignore
27
+ return await this.client.messages.create({
28
+ model,
29
+ stream,
30
+ temperature,
31
+ max_tokens: tokens,
32
+ system: instructions,
33
+ ...this.getSchemaOptions(options),
34
+ messages: input,
35
+ });
77
36
  }
78
- if (type) {
79
- return {
80
- type,
81
- text: chunk.delta?.text || ''
82
- };
37
+ async runStream(options) {
38
+ return await this.runPrompt({
39
+ ...options,
40
+ output: 'raw',
41
+ stream: true,
42
+ });
43
+ }
44
+ getTextResponse(response) {
45
+ const textBlock = response.content.find((block) => {
46
+ return block.type === 'text';
47
+ });
48
+ return textBlock?.text || null;
49
+ }
50
+ getStructuredResponse(response) {
51
+ const toolBlock = response.content.find((block) => {
52
+ return block.type === 'tool_use';
53
+ });
54
+ return toolBlock?.input || null;
55
+ }
56
+ getMessagesResponse(input, response) {
57
+ return {
58
+ messages: [
59
+ ...input,
60
+ ...response.content
61
+ .filter((item) => {
62
+ return item.type === 'text';
63
+ })
64
+ .map((item) => {
65
+ return {
66
+ role: 'assistant',
67
+ content: item.text,
68
+ };
69
+ }),
70
+ ],
71
+ };
72
+ }
73
+ normalizeStreamEvent(event) {
74
+ let { type } = event;
75
+ if (type === 'content_block_start') {
76
+ return {
77
+ type: 'start',
78
+ };
79
+ }
80
+ else if (type === 'content_block_stop') {
81
+ return {
82
+ type: 'stop',
83
+ };
84
+ }
85
+ else if (type === 'content_block_delta') {
86
+ return {
87
+ type: 'delta',
88
+ text: event.delta.text,
89
+ };
90
+ }
91
+ }
92
+ // Private
93
+ getSchemaOptions(options) {
94
+ const { output } = options;
95
+ if (output?.type) {
96
+ let schema = output;
97
+ if (schema.type === 'array') {
98
+ schema = {
99
+ type: 'object',
100
+ properties: {
101
+ items: schema,
102
+ },
103
+ required: ['items'],
104
+ additionalProperties: false,
105
+ };
106
+ }
107
+ return {
108
+ tools: [
109
+ {
110
+ name: 'schema',
111
+ description: 'Follow the schema for JSON output.',
112
+ input_schema: schema,
113
+ },
114
+ ],
115
+ tool_choice: {
116
+ type: 'tool',
117
+ name: 'schema',
118
+ },
119
+ };
120
+ }
83
121
  }
84
- }
85
122
  }
86
123
  exports.AnthropicClient = AnthropicClient;
87
- function splitMessages(messages) {
88
- const system = [];
89
- const user = [];
90
- for (let message of messages) {
91
- if (message.role === 'system') {
92
- system.push(message);
93
- } else {
94
- user.push(message);
95
- }
96
- }
97
- return {
98
- system: system.join('\n'),
99
- user
100
- };
101
- }