@bedrockio/ai 0.3.0 → 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.
- package/.claude/settings.local.json +11 -0
- package/CHANGELOG.md +21 -0
- package/README.md +58 -17
- package/__mocks__/@anthropic-ai/sdk.js +16 -22
- package/__mocks__/@google/generative-ai.js +1 -1
- package/__mocks__/openai.js +33 -28
- package/dist/cjs/BaseClient.js +242 -182
- package/dist/cjs/anthropic.js +115 -93
- package/dist/cjs/google.js +74 -80
- package/dist/cjs/index.js +23 -75
- package/dist/cjs/openai.js +114 -72
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/utils/code.js +11 -0
- package/dist/cjs/utils/json.js +53 -0
- package/dist/cjs/utils/templates.js +83 -0
- package/dist/cjs/xai.js +11 -20
- package/dist/esm/BaseClient.js +243 -0
- package/dist/esm/anthropic.js +116 -0
- package/dist/esm/google.js +75 -0
- package/dist/esm/index.js +25 -0
- package/dist/esm/openai.js +113 -0
- package/dist/esm/utils/code.js +8 -0
- package/dist/esm/utils/json.js +50 -0
- package/dist/esm/utils/templates.js +76 -0
- package/dist/esm/xai.js +10 -0
- package/eslint.config.js +2 -0
- package/package.json +18 -17
- package/src/BaseClient.js +233 -140
- package/src/anthropic.js +96 -56
- package/src/google.js +3 -6
- package/src/index.js +6 -54
- package/src/openai.js +96 -33
- package/src/utils/code.js +9 -0
- package/src/utils/json.js +58 -0
- package/src/utils/templates.js +87 -0
- package/src/xai.js +2 -9
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.esm.json +8 -0
- package/tsconfig.types.json +9 -0
- package/types/BaseClient.d.ts +67 -26
- package/types/BaseClient.d.ts.map +1 -1
- package/types/anthropic.d.ts +26 -2
- package/types/anthropic.d.ts.map +1 -1
- package/types/google.d.ts.map +1 -1
- package/types/index.d.ts +4 -11
- package/types/index.d.ts.map +1 -1
- package/types/openai.d.ts +45 -2
- package/types/openai.d.ts.map +1 -1
- package/types/utils/code.d.ts +2 -0
- package/types/utils/code.d.ts.map +1 -0
- package/types/utils/json.d.ts +2 -0
- package/types/utils/json.d.ts.map +1 -0
- package/types/utils/templates.d.ts +3 -0
- package/types/utils/templates.d.ts.map +1 -0
- package/types/utils.d.ts +4 -0
- package/types/utils.d.ts.map +1 -0
- package/types/xai.d.ts.map +1 -1
- package/vitest.config.js +10 -0
- package/dist/cjs/util.js +0 -62
- package/src/util.js +0 -60
package/dist/esm/xai.js
ADDED
package/eslint.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrockio/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Bedrock wrapper for common AI chatbots.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
-
"
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"test:live": "node --env-file=.env.live ./node_modules/.bin/vitest run",
|
|
9
9
|
"lint": "eslint",
|
|
10
10
|
"build": "scripts/build",
|
|
11
11
|
"eject": "scripts/eject",
|
|
12
|
-
"
|
|
12
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
13
|
+
"build:esm": "tsc -p tsconfig.esm.json && tsc-alias -f -p tsconfig.esm.json",
|
|
14
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
15
|
+
"prepublish": "yarn build"
|
|
13
16
|
},
|
|
14
17
|
"types": "types/index.d.ts",
|
|
15
18
|
"main": "./dist/cjs/index.js",
|
|
16
19
|
"exports": {
|
|
17
20
|
".": {
|
|
21
|
+
"types": "./types/index.d.ts",
|
|
18
22
|
"import": "./src/index.js",
|
|
19
23
|
"require": "./dist/cjs/index.js"
|
|
20
24
|
}
|
|
@@ -31,27 +35,24 @@
|
|
|
31
35
|
"url": "https://github.com/bedrockio/router"
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
|
-
"@anthropic-ai/sdk": "^0.
|
|
38
|
+
"@anthropic-ai/sdk": "^0.65.0",
|
|
35
39
|
"@google/generative-ai": "^0.21.0",
|
|
36
40
|
"glob": "^11.0.1",
|
|
37
41
|
"mustache": "^4.2.0",
|
|
38
|
-
"openai": "^
|
|
42
|
+
"openai": "^6.1.0",
|
|
43
|
+
"partial-json": "^0.1.7"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
|
-
"@
|
|
42
|
-
"@babel/core": "^7.26.0",
|
|
43
|
-
"@babel/eslint-parser": "^7.26.5",
|
|
44
|
-
"@babel/preset-env": "^7.26.0",
|
|
46
|
+
"@bedrockio/eslint-plugin": "^1.2.2",
|
|
45
47
|
"@bedrockio/prettier-config": "^1.0.2",
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"typescript": "^5.7.3"
|
|
48
|
+
"@bedrockio/yada": "^1.8.0",
|
|
49
|
+
"eslint": "^9.36.0",
|
|
50
|
+
"tsc-alias": "^1.8.16",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vitest": "^3.2.4"
|
|
52
53
|
},
|
|
53
54
|
"volta": {
|
|
54
|
-
"node": "22.
|
|
55
|
+
"node": "22.20.0",
|
|
55
56
|
"yarn": "1.22.22"
|
|
56
57
|
}
|
|
57
58
|
}
|
package/src/BaseClient.js
CHANGED
|
@@ -1,195 +1,288 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { loadTemplates,
|
|
4
|
-
|
|
5
|
-
const MESSAGES_REG = /(?:^|\n)-{3,}\s*(\w+)\s*-{3,}(.*?)(?=\n-{3,}|$)/gs;
|
|
1
|
+
import { parseCode } from './utils/code.js';
|
|
2
|
+
import { createMessageExtractor } from './utils/json.js';
|
|
3
|
+
import { loadTemplates, renderTemplate } from './utils/templates.js';
|
|
6
4
|
|
|
7
5
|
export default class BaseClient {
|
|
8
6
|
constructor(options) {
|
|
9
|
-
this.options =
|
|
7
|
+
this.options = {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
model: this.constructor.DEFAULT_MODEL,
|
|
10
|
+
...options,
|
|
11
|
+
};
|
|
10
12
|
this.templates = null;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
// Public
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
|
-
* Interpolates vars into the provided template and
|
|
15
|
-
*
|
|
16
|
-
* be omitted and will default to `"text"`.
|
|
17
|
-
* {@link https://github.com/bedrockio/ai?tab=readme-ov-file#bedrockioai Documentation}
|
|
18
|
+
* Interpolates vars into the provided template as instructions and runs the
|
|
19
|
+
* prompt.
|
|
18
20
|
*
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {string} options.model - The model to use.
|
|
21
|
-
* @param {"raw" | "text" | "json" | "messages"} [options.output] - The output to use.
|
|
22
|
-
* @param {Object.<string, any>} [options.other] - Additional props
|
|
23
|
-
* will be interpolated in the template.
|
|
21
|
+
* @param {PromptOptions} options
|
|
24
22
|
*/
|
|
25
23
|
async prompt(options) {
|
|
26
|
-
options =
|
|
27
|
-
...this.options,
|
|
28
|
-
...options,
|
|
29
|
-
};
|
|
24
|
+
options = await this.normalizeOptions(options);
|
|
30
25
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const { input, output, stream, schema } = options;
|
|
27
|
+
|
|
28
|
+
const response = await this.runPrompt(options);
|
|
29
|
+
|
|
30
|
+
if (!stream) {
|
|
31
|
+
this.debug('Response:', response);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (output === 'raw') {
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let result;
|
|
39
|
+
if (schema) {
|
|
40
|
+
result = this.getStructuredResponse(response);
|
|
41
|
+
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
if (options.hasWrappedSchema) {
|
|
44
|
+
result = result.items;
|
|
45
|
+
}
|
|
46
|
+
} else if (output === 'json') {
|
|
47
|
+
result = JSON.parse(parseCode(this.getTextResponse(response)));
|
|
48
|
+
} else {
|
|
49
|
+
result = parseCode(this.getTextResponse(response));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (output === 'messages') {
|
|
53
|
+
return {
|
|
54
|
+
result,
|
|
55
|
+
...this.getMessagesResponse(input, response),
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
/**
|
|
39
63
|
* Streams the prompt response.
|
|
64
|
+
*
|
|
65
|
+
* @param {PromptOptions & StreamOptions} options
|
|
40
66
|
* @returns {AsyncIterator}
|
|
41
67
|
*/
|
|
42
68
|
async *stream(options) {
|
|
43
|
-
|
|
69
|
+
options = await this.normalizeOptions(options);
|
|
44
70
|
|
|
45
|
-
|
|
71
|
+
const extractor = this.getMessageExtractor(options);
|
|
46
72
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const resolved = this.getStreamedChunk(chunk, started);
|
|
50
|
-
started = true;
|
|
73
|
+
try {
|
|
74
|
+
const stream = await this.runStream(options);
|
|
51
75
|
|
|
52
76
|
// @ts-ignore
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
for await (let event of stream) {
|
|
78
|
+
this.debug('Event:', event);
|
|
79
|
+
|
|
80
|
+
event = this.normalizeStreamEvent(event);
|
|
81
|
+
|
|
82
|
+
if (event) {
|
|
83
|
+
yield event;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const extractedMessages = extractor?.(event) || [];
|
|
87
|
+
|
|
88
|
+
for (let message of extractedMessages) {
|
|
89
|
+
const { key, delta, text, done } = message;
|
|
90
|
+
|
|
91
|
+
let extractEvent;
|
|
92
|
+
if (done) {
|
|
93
|
+
extractEvent = {
|
|
94
|
+
type: 'extract:done',
|
|
95
|
+
text,
|
|
96
|
+
key,
|
|
97
|
+
};
|
|
98
|
+
} else {
|
|
99
|
+
extractEvent = {
|
|
100
|
+
type: 'extract:delta',
|
|
101
|
+
delta,
|
|
102
|
+
key,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.debug('Extract:', extractEvent);
|
|
107
|
+
|
|
108
|
+
yield extractEvent;
|
|
109
|
+
}
|
|
55
110
|
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const { message, code } = error;
|
|
113
|
+
yield {
|
|
114
|
+
type: 'error',
|
|
115
|
+
code,
|
|
116
|
+
message,
|
|
117
|
+
};
|
|
56
118
|
}
|
|
57
119
|
}
|
|
58
120
|
|
|
59
|
-
async
|
|
60
|
-
const { text } = options;
|
|
121
|
+
async buildTemplate(options) {
|
|
61
122
|
const template = await this.resolveTemplate(options);
|
|
123
|
+
return renderTemplate(template, options);
|
|
124
|
+
}
|
|
62
125
|
|
|
63
|
-
|
|
64
|
-
const raw = render(template, options);
|
|
126
|
+
// Protected
|
|
65
127
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
role: role.toLowerCase(),
|
|
71
|
-
content: content.trim(),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
128
|
+
runPrompt(options) {
|
|
129
|
+
void options;
|
|
130
|
+
throw new Error('Method not implemented.');
|
|
131
|
+
}
|
|
74
132
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
133
|
+
runStream(options) {
|
|
134
|
+
void options;
|
|
135
|
+
throw new Error('Method not implemented.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getTextResponse(response) {
|
|
139
|
+
void response;
|
|
140
|
+
throw new Error('Method not implemented.');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @returns {Object}
|
|
145
|
+
*/
|
|
146
|
+
getStructuredResponse(response) {
|
|
147
|
+
void response;
|
|
148
|
+
throw new Error('Method not implemented.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @returns {Object}
|
|
153
|
+
*/
|
|
154
|
+
getMessagesResponse(input, response) {
|
|
155
|
+
void response;
|
|
156
|
+
throw new Error('Method not implemented.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @returns {Object}
|
|
161
|
+
*/
|
|
162
|
+
normalizeStreamEvent(event) {
|
|
163
|
+
void event;
|
|
164
|
+
throw new Error('Method not implemented.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Private
|
|
168
|
+
|
|
169
|
+
async normalizeOptions(options) {
|
|
170
|
+
options = {
|
|
171
|
+
input: '',
|
|
172
|
+
output: 'text',
|
|
173
|
+
...this.options,
|
|
174
|
+
...options,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
options.input = this.normalizeInput(options);
|
|
178
|
+
options.schema = this.normalizeSchema(options);
|
|
179
|
+
options.instructions ||= await this.resolveInstructions(options);
|
|
180
|
+
|
|
181
|
+
return options;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
normalizeInput(options) {
|
|
185
|
+
let { input = '', output } = options;
|
|
186
|
+
|
|
187
|
+
if (typeof input === 'string') {
|
|
188
|
+
if (output === 'json') {
|
|
189
|
+
input += '\nOutput only valid JSON.';
|
|
80
190
|
}
|
|
81
191
|
|
|
82
|
-
|
|
83
|
-
} else if (text) {
|
|
84
|
-
return [
|
|
192
|
+
input = [
|
|
85
193
|
{
|
|
86
194
|
role: 'user',
|
|
87
|
-
content:
|
|
195
|
+
content: input,
|
|
88
196
|
},
|
|
89
197
|
];
|
|
90
|
-
} else {
|
|
91
|
-
throw new Error('No input provided.');
|
|
92
198
|
}
|
|
93
|
-
}
|
|
94
199
|
|
|
95
|
-
|
|
96
|
-
const template = await this.resolveTemplate(options);
|
|
97
|
-
return render(template, options);
|
|
200
|
+
return input;
|
|
98
201
|
}
|
|
99
202
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.templates ||= await loadTemplates(templates);
|
|
103
|
-
}
|
|
203
|
+
normalizeSchema(options) {
|
|
204
|
+
let { schema } = options;
|
|
104
205
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (template) {
|
|
108
|
-
return template;
|
|
109
|
-
} else if (file?.endsWith('.md')) {
|
|
110
|
-
return await loadTemplate(file);
|
|
111
|
-
} else if (file) {
|
|
112
|
-
await this.loadTemplates();
|
|
113
|
-
return this.templates[file];
|
|
206
|
+
if (!schema) {
|
|
207
|
+
return;
|
|
114
208
|
}
|
|
115
|
-
}
|
|
116
209
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...options,
|
|
120
|
-
output: 'raw',
|
|
121
|
-
stream: true,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
210
|
+
// Convert to JSON schema.
|
|
211
|
+
schema = schema.toJSON?.() || schema;
|
|
124
212
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
213
|
+
if (schema?.type === 'array') {
|
|
214
|
+
schema = {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
items: schema,
|
|
218
|
+
},
|
|
219
|
+
required: ['items'],
|
|
220
|
+
additionalProperties: false,
|
|
221
|
+
};
|
|
222
|
+
options.hasWrappedSchema = true;
|
|
223
|
+
}
|
|
129
224
|
|
|
130
|
-
|
|
131
|
-
void chunk;
|
|
132
|
-
void started;
|
|
133
|
-
new Error('Method not implemented.');
|
|
225
|
+
return schema;
|
|
134
226
|
}
|
|
135
|
-
}
|
|
136
227
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
228
|
+
getMessageExtractor(options) {
|
|
229
|
+
const { extractMessages } = options;
|
|
230
|
+
if (!extractMessages) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const messageExtractor = createMessageExtractor([extractMessages]);
|
|
234
|
+
return (event) => {
|
|
235
|
+
if (event?.type === 'delta') {
|
|
236
|
+
return messageExtractor(event.delta);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
142
240
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
241
|
+
debug(message, arg) {
|
|
242
|
+
if (this.options.debug) {
|
|
243
|
+
// TODO: replace with logger when opentelemetry is removed
|
|
244
|
+
// eslint-disable-next-line
|
|
245
|
+
console.debug(`${message}\n${JSON.stringify(arg, null, 2)}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
147
248
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const result = {};
|
|
153
|
-
for (let [key, value] of Object.entries(params)) {
|
|
154
|
-
if (Array.isArray(value)) {
|
|
155
|
-
value = mapArray(value);
|
|
156
|
-
} else if (typeof value === 'object') {
|
|
157
|
-
value = JSON.stringify(value, null, 2);
|
|
249
|
+
async resolveInstructions(options) {
|
|
250
|
+
if (options.template) {
|
|
251
|
+
const template = await this.resolveTemplate(options);
|
|
252
|
+
return renderTemplate(template, options);
|
|
158
253
|
}
|
|
159
|
-
result[key] = value;
|
|
160
254
|
}
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
255
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.map((el) => {
|
|
169
|
-
return `- ${el}`;
|
|
170
|
-
})
|
|
171
|
-
.join('\n');
|
|
256
|
+
async resolveTemplate(options) {
|
|
257
|
+
const { template } = options;
|
|
258
|
+
await this.loadTemplates();
|
|
259
|
+
return this.templates[template] || template;
|
|
172
260
|
}
|
|
173
|
-
return arr;
|
|
174
|
-
}
|
|
175
261
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// interpolated and re-interpolated later.
|
|
181
|
-
function wrapProxy(params) {
|
|
182
|
-
return new Proxy(params, {
|
|
183
|
-
has() {
|
|
184
|
-
return true;
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
get(target, prop) {
|
|
188
|
-
if (prop in target) {
|
|
189
|
-
return target[prop];
|
|
190
|
-
} else {
|
|
191
|
-
return `{{{${prop.toString()}}}}`;
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
});
|
|
262
|
+
async loadTemplates() {
|
|
263
|
+
const { templates } = this.options;
|
|
264
|
+
this.templates ||= await loadTemplates(templates);
|
|
265
|
+
}
|
|
195
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @typedef {Object} PromptOptions
|
|
270
|
+
* @property {string|PromptMessage[]} input - Input to use.
|
|
271
|
+
* @property {string} [model] - The model to use.
|
|
272
|
+
* @property {boolean} stream - Stream response.
|
|
273
|
+
* @property {Object} [schema] - A JSON schema compatible object that defines the output shape.
|
|
274
|
+
* @property {"raw" | "text" | "json" | "messages"} [output] - The return value type.
|
|
275
|
+
* @property {Object} [params] - Params to be interpolated into the template.
|
|
276
|
+
* May also be passed as additional props to options.
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @typedef {Object} StreamOptions
|
|
281
|
+
* @property {string} [extractMessages] - Key in JSON response to extract a message stream from.
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @typedef {Object} PromptMessage
|
|
286
|
+
* @property {"system" | "user" | "assistant"} role
|
|
287
|
+
* @property {string} content
|
|
288
|
+
*/
|