boids-sdk 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.
- package/README.md +74 -0
- package/bin/boids.js +433 -0
- package/package.json +45 -0
- package/src/index.d.ts +50 -0
- package/src/index.js +231 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# boids-sdk
|
|
2
|
+
|
|
3
|
+
JavaScript SDK and CLI for the Boids API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install boids-sdk
|
|
9
|
+
export BOIDS_API_KEY="..."
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## CLI
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
boids agent:@iris-wei-org/my-doppelganger "Introduce yourself in one sentence."
|
|
16
|
+
boids search "global launch growth agent" --limit 5
|
|
17
|
+
boids run "Create a launch plan for a developer tool."
|
|
18
|
+
boids agent:@iris-wei-org/my-doppelganger "Remember my name is Ada." --show-response-id
|
|
19
|
+
boids agent:@iris-wei-org/my-doppelganger "What is my name?" --prev resp_...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`boids run` searches `/v1/market/search`, selects the first returned agent, and
|
|
23
|
+
then sends your prompt to `/v1/responses`.
|
|
24
|
+
|
|
25
|
+
## SDK
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { Boids } from "boids-sdk";
|
|
29
|
+
|
|
30
|
+
const client = new Boids();
|
|
31
|
+
|
|
32
|
+
const response = await client.responses.create({
|
|
33
|
+
model: "agent:@iris-wei-org/my-doppelganger",
|
|
34
|
+
input: "Introduce yourself in one sentence.",
|
|
35
|
+
});
|
|
36
|
+
console.log(response);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Streaming:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
for await (const event of client.responses.create({
|
|
43
|
+
model: "agent:@iris-wei-org/my-doppelganger",
|
|
44
|
+
input: "Introduce yourself in one sentence.",
|
|
45
|
+
stream: true,
|
|
46
|
+
})) {
|
|
47
|
+
console.log(event.data);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Market search:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
const agents = await client.market.search({
|
|
55
|
+
query: "global launch growth agent",
|
|
56
|
+
limit: 5,
|
|
57
|
+
});
|
|
58
|
+
console.log(agents.data.items[0].model_name);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Conversation context:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const first = await client.responses.create({
|
|
65
|
+
model: "agent:@iris-wei-org/my-doppelganger",
|
|
66
|
+
input: "Remember my name is Ada.",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const second = await client.responses.create({
|
|
70
|
+
model: "agent:@iris-wei-org/my-doppelganger",
|
|
71
|
+
input: "What is my name?",
|
|
72
|
+
previous_response_id: first.id,
|
|
73
|
+
});
|
|
74
|
+
```
|
package/bin/boids.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Boids, DEFAULT_BASE_URL, extractText } from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
const env = process.env;
|
|
5
|
+
|
|
6
|
+
async function main(argv) {
|
|
7
|
+
if (looksLikeShortcut(argv)) {
|
|
8
|
+
return runShortcut(argv);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const command = argv.shift();
|
|
12
|
+
|
|
13
|
+
if (!command || command === "-h" || command === "--help") {
|
|
14
|
+
usage();
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (command === "ask") {
|
|
19
|
+
const options = parseOptions(argv);
|
|
20
|
+
const model = requireModel(options.model);
|
|
21
|
+
const input = options._.join(" ");
|
|
22
|
+
if (!input) {
|
|
23
|
+
throw new Error("Missing input text.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const client = makeClient(options);
|
|
27
|
+
const result = client.responses.create({
|
|
28
|
+
model,
|
|
29
|
+
input,
|
|
30
|
+
stream: options.stream ?? true,
|
|
31
|
+
previous_response_id: options.previousResponseId,
|
|
32
|
+
});
|
|
33
|
+
await printResult(result, Boolean(options.json), {
|
|
34
|
+
showResponseId: Boolean(options.showResponseId),
|
|
35
|
+
});
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (command === "search") {
|
|
40
|
+
const options = parseOptions(argv, { envModel: false });
|
|
41
|
+
const query = options.query ?? options._.join(" ");
|
|
42
|
+
if (!query) {
|
|
43
|
+
throw new Error("Missing search query.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const client = makeClient(options);
|
|
47
|
+
const result = await client.market.search({
|
|
48
|
+
query,
|
|
49
|
+
limit: Number(options.limit ?? 5),
|
|
50
|
+
});
|
|
51
|
+
printSearchResult(result, Boolean(options.json));
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (command === "run" || command === "auto") {
|
|
56
|
+
const options = parseOptions(argv, { envModel: false });
|
|
57
|
+
const input = options.input ?? options._.join(" ");
|
|
58
|
+
if (!input) {
|
|
59
|
+
throw new Error("Missing input text.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const client = makeClient(options);
|
|
63
|
+
const result = await runWithBestAgent(client, {
|
|
64
|
+
input,
|
|
65
|
+
searchQuery: options.searchQuery ?? input,
|
|
66
|
+
limit: Number(options.limit ?? 1),
|
|
67
|
+
stream: options.stream ?? true,
|
|
68
|
+
previousResponseId: options.previousResponseId,
|
|
69
|
+
showAgent: !options.quietAgent && !options.json,
|
|
70
|
+
});
|
|
71
|
+
await printResult(result, Boolean(options.json), {
|
|
72
|
+
showResponseId: Boolean(options.showResponseId),
|
|
73
|
+
});
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (command === "responses" && argv[0] === "create") {
|
|
78
|
+
argv.shift();
|
|
79
|
+
const options = parseOptions(argv);
|
|
80
|
+
const model = requireModel(options.model);
|
|
81
|
+
const input = options.input ?? options._.join(" ");
|
|
82
|
+
if (!input) {
|
|
83
|
+
throw new Error("Missing --input or input text.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const client = makeClient(options);
|
|
87
|
+
const result = client.responses.create({
|
|
88
|
+
model,
|
|
89
|
+
input,
|
|
90
|
+
stream: Boolean(options.stream),
|
|
91
|
+
previous_response_id: options.previousResponseId,
|
|
92
|
+
});
|
|
93
|
+
await printResult(result, Boolean(options.json), {
|
|
94
|
+
showResponseId: Boolean(options.showResponseId),
|
|
95
|
+
});
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw new Error(`Unknown command: ${command}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function runShortcut(argv) {
|
|
103
|
+
const options = parseOptions(argv, { envModel: false });
|
|
104
|
+
const model = options.model ?? options._.shift();
|
|
105
|
+
const input = options.input ?? options._.join(" ");
|
|
106
|
+
|
|
107
|
+
if (!model) {
|
|
108
|
+
throw new Error("Missing agent model.");
|
|
109
|
+
}
|
|
110
|
+
if (!input) {
|
|
111
|
+
throw new Error("Missing input text.");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const client = makeClient(options);
|
|
115
|
+
const result = client.responses.create({
|
|
116
|
+
model,
|
|
117
|
+
input,
|
|
118
|
+
stream: options.stream ?? true,
|
|
119
|
+
previous_response_id: options.previousResponseId,
|
|
120
|
+
});
|
|
121
|
+
await printResult(result, Boolean(options.json), {
|
|
122
|
+
showResponseId: Boolean(options.showResponseId),
|
|
123
|
+
});
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function makeClient(options) {
|
|
128
|
+
return new Boids({
|
|
129
|
+
apiKey: options.apiKey ?? env.BOIDS_API_KEY,
|
|
130
|
+
baseURL: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runWithBestAgent(client, options) {
|
|
135
|
+
const searchResult = await client.market.search({
|
|
136
|
+
query: options.searchQuery,
|
|
137
|
+
limit: options.limit,
|
|
138
|
+
});
|
|
139
|
+
const item = marketItems(searchResult)[0];
|
|
140
|
+
if (!item) {
|
|
141
|
+
throw new Error(`No agents found for: ${options.searchQuery}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const model = agentModel(item);
|
|
145
|
+
if (!model) {
|
|
146
|
+
throw new Error("Best market result did not include a usable model.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (options.showAgent) {
|
|
150
|
+
const title = item.title ?? item.id ?? model;
|
|
151
|
+
console.error(`Selected agent: ${title} (${model})`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return client.responses.create({
|
|
155
|
+
model,
|
|
156
|
+
input: options.input,
|
|
157
|
+
stream: options.stream,
|
|
158
|
+
previous_response_id: options.previousResponseId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function printSearchResult(result, asJSON) {
|
|
163
|
+
if (asJSON) {
|
|
164
|
+
console.log(JSON.stringify(result, null, 2));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const items = marketItems(result);
|
|
169
|
+
if (items.length === 0) {
|
|
170
|
+
console.log("No agents found.");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
items.forEach((item, index) => {
|
|
175
|
+
const title = item.title ?? item.id ?? "Untitled agent";
|
|
176
|
+
const model = agentModel(item) ?? "unknown";
|
|
177
|
+
console.log(`${index + 1}. ${title}`);
|
|
178
|
+
console.log(` model: ${model}`);
|
|
179
|
+
if (item.description) {
|
|
180
|
+
console.log(` ${item.description}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function marketItems(result) {
|
|
186
|
+
const items = result?.data?.items;
|
|
187
|
+
return Array.isArray(items) ? items.filter((item) => item && typeof item === "object") : [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function agentModel(item) {
|
|
191
|
+
if (typeof item.model_name === "string" && item.model_name.startsWith("agent:")) {
|
|
192
|
+
return item.model_name;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const agentId = item.agent_id ?? item.id;
|
|
196
|
+
if (typeof agentId === "string" && agentId) {
|
|
197
|
+
return `agent:${agentId}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof item.model_name === "string" && item.model_name) {
|
|
201
|
+
return item.model_name;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function printResult(result, asJSON, { showResponseId = false } = {}) {
|
|
208
|
+
if (isAsyncIterable(result)) {
|
|
209
|
+
let wroteText = false;
|
|
210
|
+
let fallbackText;
|
|
211
|
+
let responseId;
|
|
212
|
+
|
|
213
|
+
for await (const event of result) {
|
|
214
|
+
if (event.event?.endsWith(".completed")) {
|
|
215
|
+
fallbackText = extractText(event.data);
|
|
216
|
+
responseId = extractResponseId(event.data);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (asJSON) {
|
|
220
|
+
console.log(JSON.stringify(event));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const delta = extractDelta(event.data);
|
|
225
|
+
if (delta !== undefined) {
|
|
226
|
+
process.stdout.write(delta);
|
|
227
|
+
wroteText = true;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (wroteText) {
|
|
234
|
+
process.stdout.write("\n");
|
|
235
|
+
} else if (fallbackText) {
|
|
236
|
+
console.log(fallbackText);
|
|
237
|
+
}
|
|
238
|
+
if (showResponseId && responseId) {
|
|
239
|
+
console.error(`Response ID: ${responseId}`);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const value = await result;
|
|
245
|
+
if (asJSON) {
|
|
246
|
+
console.log(JSON.stringify(value, null, 2));
|
|
247
|
+
if (showResponseId) {
|
|
248
|
+
printResponseId(value);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const text = extractText(value);
|
|
254
|
+
console.log(text ?? JSON.stringify(value, null, 2));
|
|
255
|
+
if (showResponseId) {
|
|
256
|
+
printResponseId(value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseOptions(args, { envModel = true } = {}) {
|
|
261
|
+
const options = { _: [] };
|
|
262
|
+
|
|
263
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
264
|
+
const arg = args[index];
|
|
265
|
+
|
|
266
|
+
if (arg === "--stream") {
|
|
267
|
+
options.stream = true;
|
|
268
|
+
} else if (arg === "--no-stream") {
|
|
269
|
+
options.stream = false;
|
|
270
|
+
} else if (arg === "--json") {
|
|
271
|
+
options.json = true;
|
|
272
|
+
} else if (arg === "--show-response-id") {
|
|
273
|
+
options.showResponseId = true;
|
|
274
|
+
} else if (arg === "--quiet-agent") {
|
|
275
|
+
options.quietAgent = true;
|
|
276
|
+
} else if (arg === "--show-agent") {
|
|
277
|
+
options.quietAgent = false;
|
|
278
|
+
} else if (arg === "-m" || arg === "--model") {
|
|
279
|
+
options.model = args[++index];
|
|
280
|
+
} else if (arg === "-i" || arg === "--input") {
|
|
281
|
+
options.input = args[++index];
|
|
282
|
+
} else if (arg === "-q" || arg === "--query") {
|
|
283
|
+
options.query = args[++index];
|
|
284
|
+
} else if (arg === "--search-query") {
|
|
285
|
+
options.searchQuery = args[++index];
|
|
286
|
+
} else if (arg === "--limit") {
|
|
287
|
+
options.limit = args[++index];
|
|
288
|
+
} else if (arg === "--previous-response-id" || arg === "--prev") {
|
|
289
|
+
options.previousResponseId = args[++index];
|
|
290
|
+
} else if (arg === "--api-key") {
|
|
291
|
+
options.apiKey = args[++index];
|
|
292
|
+
} else if (arg === "--base-url") {
|
|
293
|
+
options.baseUrl = args[++index];
|
|
294
|
+
} else if (arg.startsWith("--model=")) {
|
|
295
|
+
options.model = arg.slice("--model=".length);
|
|
296
|
+
} else if (arg.startsWith("--input=")) {
|
|
297
|
+
options.input = arg.slice("--input=".length);
|
|
298
|
+
} else if (arg.startsWith("--query=")) {
|
|
299
|
+
options.query = arg.slice("--query=".length);
|
|
300
|
+
} else if (arg.startsWith("--search-query=")) {
|
|
301
|
+
options.searchQuery = arg.slice("--search-query=".length);
|
|
302
|
+
} else if (arg.startsWith("--limit=")) {
|
|
303
|
+
options.limit = arg.slice("--limit=".length);
|
|
304
|
+
} else if (arg.startsWith("--previous-response-id=")) {
|
|
305
|
+
options.previousResponseId = arg.slice("--previous-response-id=".length);
|
|
306
|
+
} else if (arg.startsWith("--prev=")) {
|
|
307
|
+
options.previousResponseId = arg.slice("--prev=".length);
|
|
308
|
+
} else if (arg.startsWith("--api-key=")) {
|
|
309
|
+
options.apiKey = arg.slice("--api-key=".length);
|
|
310
|
+
} else if (arg.startsWith("--base-url=")) {
|
|
311
|
+
options.baseUrl = arg.slice("--base-url=".length);
|
|
312
|
+
} else {
|
|
313
|
+
options._.push(arg);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (envModel) {
|
|
318
|
+
options.model ??= env.BOIDS_MODEL;
|
|
319
|
+
}
|
|
320
|
+
return options;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function requireModel(model) {
|
|
324
|
+
if (model) {
|
|
325
|
+
return model;
|
|
326
|
+
}
|
|
327
|
+
throw new Error("Missing --model or BOIDS_MODEL.");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function isAsyncIterable(value) {
|
|
331
|
+
return value && typeof value[Symbol.asyncIterator] === "function";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function extractDelta(value) {
|
|
335
|
+
if (value && typeof value === "object" && typeof value.delta === "string") {
|
|
336
|
+
return value.delta;
|
|
337
|
+
}
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function extractResponseId(value) {
|
|
342
|
+
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
343
|
+
return value.id;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function printResponseId(value) {
|
|
349
|
+
const responseId = extractResponseId(value);
|
|
350
|
+
if (responseId) {
|
|
351
|
+
console.error(`Response ID: ${responseId}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function looksLikeShortcut(args) {
|
|
356
|
+
const first = firstPositional(args);
|
|
357
|
+
return first !== undefined && !["ask", "responses", "search", "run", "auto"].includes(first);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function firstPositional(args) {
|
|
361
|
+
let skipNext = false;
|
|
362
|
+
const optionsWithValues = new Set([
|
|
363
|
+
"--api-key",
|
|
364
|
+
"--base-url",
|
|
365
|
+
"--model",
|
|
366
|
+
"-m",
|
|
367
|
+
"--input",
|
|
368
|
+
"-i",
|
|
369
|
+
"--query",
|
|
370
|
+
"-q",
|
|
371
|
+
"--search-query",
|
|
372
|
+
"--limit",
|
|
373
|
+
"--previous-response-id",
|
|
374
|
+
"--prev",
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
for (const arg of args) {
|
|
378
|
+
if (skipNext) {
|
|
379
|
+
skipNext = false;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (optionsWithValues.has(arg)) {
|
|
384
|
+
skipNext = true;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (
|
|
389
|
+
arg.startsWith("--api-key=") ||
|
|
390
|
+
arg.startsWith("--base-url=") ||
|
|
391
|
+
arg.startsWith("--model=") ||
|
|
392
|
+
arg.startsWith("--input=") ||
|
|
393
|
+
arg.startsWith("--query=") ||
|
|
394
|
+
arg.startsWith("--search-query=") ||
|
|
395
|
+
arg.startsWith("--limit=") ||
|
|
396
|
+
arg.startsWith("--previous-response-id=") ||
|
|
397
|
+
arg.startsWith("--prev=")
|
|
398
|
+
) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (arg.startsWith("-")) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return arg;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function usage() {
|
|
413
|
+
console.log(`Usage:
|
|
414
|
+
boids <agent-model> <input>
|
|
415
|
+
boids search <query> [--limit 5]
|
|
416
|
+
boids run <input> [--search-query <query>] [--prev <response-id>]
|
|
417
|
+
boids ask --model <model> [--no-stream] <input>
|
|
418
|
+
boids responses create --model <model> --input <input> [--stream] [--prev <response-id>]
|
|
419
|
+
|
|
420
|
+
Environment:
|
|
421
|
+
BOIDS_API_KEY Required API key
|
|
422
|
+
BOIDS_MODEL Optional default model`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
main(process.argv.slice(2)).then(
|
|
426
|
+
(code) => {
|
|
427
|
+
process.exitCode = code;
|
|
428
|
+
},
|
|
429
|
+
(error) => {
|
|
430
|
+
console.error(error.message);
|
|
431
|
+
process.exitCode = 1;
|
|
432
|
+
},
|
|
433
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boids-sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "JavaScript SDK and CLI for the Boids Responses API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"boids": "./bin/boids.js"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+ssh://git@github.com/NevaMind-AI/boids-sdk.git",
|
|
24
|
+
"directory": "js"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/NevaMind-AI/boids-sdk#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/NevaMind-AI/boids-sdk/issues"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"package.json"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"check": "node --check src/index.js && node --check bin/boids.js"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"boids",
|
|
41
|
+
"sdk",
|
|
42
|
+
"cli",
|
|
43
|
+
"responses"
|
|
44
|
+
]
|
|
45
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface BoidsOptions {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
baseURL?: string;
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ResponseCreateParams {
|
|
9
|
+
model: string;
|
|
10
|
+
input: unknown;
|
|
11
|
+
stream?: boolean;
|
|
12
|
+
previous_response_id?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ResponseEvent {
|
|
17
|
+
event?: string;
|
|
18
|
+
data: unknown;
|
|
19
|
+
raw: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MarketSearchParams {
|
|
23
|
+
query: string;
|
|
24
|
+
limit?: number;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class BoidsError extends Error {}
|
|
29
|
+
|
|
30
|
+
export class BoidsAPIError extends BoidsError {
|
|
31
|
+
status: number;
|
|
32
|
+
body: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Boids {
|
|
36
|
+
constructor(options?: BoidsOptions);
|
|
37
|
+
responses: {
|
|
38
|
+
create(params: ResponseCreateParams): Promise<unknown> | AsyncIterable<ResponseEvent>;
|
|
39
|
+
};
|
|
40
|
+
market: {
|
|
41
|
+
search(params: MarketSearchParams | string): Promise<unknown>;
|
|
42
|
+
};
|
|
43
|
+
createResponse(params: ResponseCreateParams): Promise<unknown> | AsyncIterable<ResponseEvent>;
|
|
44
|
+
searchMarket(params: MarketSearchParams | string): Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseSSE(response: Response): AsyncIterable<ResponseEvent>;
|
|
48
|
+
export function extractText(value: unknown): string | undefined;
|
|
49
|
+
|
|
50
|
+
export const DEFAULT_BASE_URL: string;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://api.boids.so/v1";
|
|
2
|
+
const USER_AGENT = "boids-sdk-js/0.1.1";
|
|
3
|
+
|
|
4
|
+
export class BoidsError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "BoidsError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BoidsAPIError extends BoidsError {
|
|
12
|
+
constructor(status, body) {
|
|
13
|
+
super(`Boids API error ${status}: ${body}`);
|
|
14
|
+
this.name = "BoidsAPIError";
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.body = body;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Boids {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
const env = typeof process === "undefined" ? {} : process.env;
|
|
23
|
+
|
|
24
|
+
this.apiKey = options.apiKey ?? env.BOIDS_API_KEY;
|
|
25
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
26
|
+
this.fetch = options.fetch ?? globalThis.fetch;
|
|
27
|
+
this.headers = options.headers ?? {};
|
|
28
|
+
|
|
29
|
+
this.responses = {
|
|
30
|
+
create: (params) => this.createResponse(params),
|
|
31
|
+
};
|
|
32
|
+
this.market = {
|
|
33
|
+
search: (params) => this.searchMarket(params),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createResponse(params = {}) {
|
|
38
|
+
const body = withoutUndefined({ ...params });
|
|
39
|
+
if (body.stream) {
|
|
40
|
+
return this.streamResponse(body);
|
|
41
|
+
}
|
|
42
|
+
return this.requestJSON("/responses", body);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async requestJSON(path, body) {
|
|
46
|
+
const response = await this.request(path, body);
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
if (!text) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
} catch {
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
searchMarket(params = {}) {
|
|
60
|
+
const body = typeof params === "string" ? { query: params } : { ...params };
|
|
61
|
+
body.limit ??= 5;
|
|
62
|
+
return this.requestJSON("/market/search", body);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async *streamResponse(body) {
|
|
66
|
+
const response = await this.request("/responses", { ...body, stream: true });
|
|
67
|
+
yield* parseSSE(response);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async request(path, body) {
|
|
71
|
+
if (!this.apiKey) {
|
|
72
|
+
throw new BoidsError("Missing BOIDS_API_KEY or apiKey.");
|
|
73
|
+
}
|
|
74
|
+
if (!this.fetch) {
|
|
75
|
+
throw new BoidsError("No fetch implementation available.");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const response = await this.fetch(`${this.baseURL}${path}`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
Accept: "text/event-stream, application/json",
|
|
84
|
+
"User-Agent": USER_AGENT,
|
|
85
|
+
...this.headers,
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify(withoutUndefined(body)),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new BoidsAPIError(response.status, await response.text());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function* parseSSE(response) {
|
|
99
|
+
const decoder = new TextDecoder();
|
|
100
|
+
let buffer = "";
|
|
101
|
+
|
|
102
|
+
for await (const chunk of readBody(response.body)) {
|
|
103
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
104
|
+
const parts = buffer.split(/\r?\n\r?\n/);
|
|
105
|
+
buffer = parts.pop() ?? "";
|
|
106
|
+
|
|
107
|
+
for (const block of parts) {
|
|
108
|
+
const event = parseSSEBlock(block);
|
|
109
|
+
if (!event) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (event.raw === "[DONE]") {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
yield event;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
buffer += decoder.decode();
|
|
120
|
+
if (buffer.trim()) {
|
|
121
|
+
const event = parseSSEBlock(buffer);
|
|
122
|
+
if (event && event.raw !== "[DONE]") {
|
|
123
|
+
yield event;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function* readBody(body) {
|
|
129
|
+
if (!body) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof body.getReader === "function") {
|
|
134
|
+
const reader = body.getReader();
|
|
135
|
+
try {
|
|
136
|
+
while (true) {
|
|
137
|
+
const { done, value } = await reader.read();
|
|
138
|
+
if (done) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (value) {
|
|
142
|
+
yield value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
reader.releaseLock();
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for await (const chunk of body) {
|
|
152
|
+
yield chunk;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseSSEBlock(block) {
|
|
157
|
+
const lines = block.split(/\r?\n/);
|
|
158
|
+
const dataLines = [];
|
|
159
|
+
let eventName;
|
|
160
|
+
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
if (!line || line.startsWith(":")) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const index = line.indexOf(":");
|
|
167
|
+
const field = index === -1 ? line : line.slice(0, index);
|
|
168
|
+
let value = index === -1 ? "" : line.slice(index + 1);
|
|
169
|
+
if (value.startsWith(" ")) {
|
|
170
|
+
value = value.slice(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (field === "event") {
|
|
174
|
+
eventName = value;
|
|
175
|
+
} else if (field === "data") {
|
|
176
|
+
dataLines.push(value);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (dataLines.length === 0) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const raw = dataLines.join("\n");
|
|
185
|
+
let data = raw;
|
|
186
|
+
if (raw !== "[DONE]") {
|
|
187
|
+
try {
|
|
188
|
+
data = JSON.parse(raw);
|
|
189
|
+
} catch {
|
|
190
|
+
data = raw;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { event: eventName, data, raw };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function extractText(value) {
|
|
198
|
+
if (typeof value === "string") {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
const parts = value.map(extractText).filter(Boolean);
|
|
204
|
+
return parts.length > 0 ? parts.join("") : undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (value && typeof value === "object") {
|
|
208
|
+
for (const key of ["delta", "text", "output_text"]) {
|
|
209
|
+
if (typeof value[key] === "string") {
|
|
210
|
+
return value[key];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const key of ["content", "output", "message"]) {
|
|
215
|
+
const text = extractText(value[key]);
|
|
216
|
+
if (text) {
|
|
217
|
+
return text;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function withoutUndefined(value) {
|
|
226
|
+
return Object.fromEntries(
|
|
227
|
+
Object.entries(value).filter(([, item]) => item !== undefined),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { DEFAULT_BASE_URL };
|