dyadic 1.0.0 → 1.1.0
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 +43 -0
- package/dist/index.d.ts +146 -1
- package/dist/index.js +62 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,4 +13,47 @@ const client = new Dyadic({ apiKey: process.env.DYADIC_API_KEY });
|
|
|
13
13
|
console.log(await client.ask("Hello!", { model: "iris" }));
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## Runs: your own agent, Dyadic's tools, your functions
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const run = await client.runs.run({
|
|
20
|
+
agent: { id: "helpdesk", name: "Helpdesk", instructions: "You answer customers of a small shop." },
|
|
21
|
+
messages: [{ role: "user", content: "Where is order 1042?" }],
|
|
22
|
+
builtin_tools: ["data.web_search"],
|
|
23
|
+
functions: {
|
|
24
|
+
lookup_order: {
|
|
25
|
+
description: "Look up an order by id.",
|
|
26
|
+
parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"] },
|
|
27
|
+
handler: async ({ order_id }) => ({ order_id, status: "shipped" }),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
end_user: "customer-17",
|
|
31
|
+
});
|
|
32
|
+
console.log(run.output_text);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`client.runs.create({ ..., stream: true })` streams the run's events.
|
|
36
|
+
|
|
37
|
+
## Connected apps: saving into a person's Workspace
|
|
38
|
+
|
|
39
|
+
With the person's approval, an application can save into their Dyadic
|
|
40
|
+
Workspace, naming them by the Discord account linked to their Dyadic account.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const status = await client.connections.discord(discordId);
|
|
44
|
+
if (!status.authorized) console.log("Allow this app:", status.authorize_url); // show the person this link
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const saved = await client.workspace.saves.create({ discord_id: discordId, kind: "message", title: "From #general",
|
|
48
|
+
text: "Worth keeping", url: "https://discord.com/channels/1/2/3" });
|
|
49
|
+
console.log(saved.url);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error instanceof DyadicError && error.code === "authorization_required") console.log("Allow this app:", error.body.authorize_url);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`kind` is `message`, `conversation` (with `messages`, saved as a chat the
|
|
56
|
+
person can continue unless `as_chat: false`), `image` or `file` (with `files`:
|
|
57
|
+
base64, up to 10 of 8 MB).
|
|
58
|
+
|
|
16
59
|
Full guide: https://dyadic.app/developers/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.1.0";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://dyadic.app/api/openai/v1";
|
|
3
3
|
export type Model = "iris" | "iris-think" | (string & {});
|
|
4
4
|
export type Role = "system" | "developer" | "user" | "assistant" | "tool";
|
|
@@ -347,10 +347,120 @@ export interface Batch {
|
|
|
347
347
|
expires_at: number | null;
|
|
348
348
|
metadata: Record<string, string>;
|
|
349
349
|
}
|
|
350
|
+
/** Dyadic's built-in tools an application's run may use. */
|
|
351
|
+
export type BuiltinTool = "data.web_search" | "data.web_fetch" | "image.generate" | "vision.describe_image";
|
|
352
|
+
/** Your own agent, sent with the run: its name and instructions, optional
|
|
353
|
+
* behaviour overrides (tool_rules, safety, style, citations, research: text to
|
|
354
|
+
* replace the default, or false to turn it off) and model choices. */
|
|
355
|
+
export interface AgentConfig {
|
|
356
|
+
id: string;
|
|
357
|
+
name: string;
|
|
358
|
+
instructions?: string;
|
|
359
|
+
behaviour?: Record<string, string | false>;
|
|
360
|
+
models?: Record<string, string>;
|
|
361
|
+
tools?: Record<string, boolean>;
|
|
362
|
+
}
|
|
363
|
+
export interface RunParams {
|
|
364
|
+
agent: string | AgentConfig;
|
|
365
|
+
messages: ChatMessage[];
|
|
366
|
+
builtin_tools?: BuiltinTool[];
|
|
367
|
+
functions?: Tool[];
|
|
368
|
+
end_user?: string;
|
|
369
|
+
conversation_id?: string;
|
|
370
|
+
max_tokens?: number;
|
|
371
|
+
temperature?: number;
|
|
372
|
+
think?: boolean;
|
|
373
|
+
research?: "auto" | "deep";
|
|
374
|
+
stream?: boolean;
|
|
375
|
+
/** Your own cap on what this run may spend, in USD (for example what your user has left). */
|
|
376
|
+
max_cost_usd?: number;
|
|
377
|
+
/** Ask for up to four suggested follow-up questions (`followups` on the completed run). */
|
|
378
|
+
followups?: boolean;
|
|
379
|
+
}
|
|
380
|
+
export interface Run {
|
|
381
|
+
object: "run";
|
|
382
|
+
type: "run.completed";
|
|
383
|
+
run_id: string;
|
|
384
|
+
status: "completed" | "requires_action";
|
|
385
|
+
agent: string;
|
|
386
|
+
output_text: string;
|
|
387
|
+
function_calls: ToolCall[];
|
|
388
|
+
messages: ChatMessage[];
|
|
389
|
+
images: {
|
|
390
|
+
url: string;
|
|
391
|
+
prompt?: string;
|
|
392
|
+
quality?: string;
|
|
393
|
+
}[];
|
|
394
|
+
sources: {
|
|
395
|
+
title?: string;
|
|
396
|
+
url: string;
|
|
397
|
+
}[];
|
|
398
|
+
tools: string[];
|
|
399
|
+
model: string;
|
|
400
|
+
models: string[];
|
|
401
|
+
usage: Usage;
|
|
402
|
+
credits: number;
|
|
403
|
+
cost_usd: number;
|
|
404
|
+
estimated: boolean;
|
|
405
|
+
model_calls: number;
|
|
406
|
+
operation_id: string;
|
|
407
|
+
seconds: number;
|
|
408
|
+
log_id?: string;
|
|
409
|
+
followups?: string[];
|
|
410
|
+
}
|
|
411
|
+
export interface RunEvent {
|
|
412
|
+
type: string;
|
|
413
|
+
[key: string]: unknown;
|
|
414
|
+
}
|
|
415
|
+
/** A function your application runs: its schema and the handler called with the parsed arguments. */
|
|
416
|
+
export interface FunctionHandler {
|
|
417
|
+
description?: string;
|
|
418
|
+
parameters?: Record<string, unknown>;
|
|
419
|
+
handler: (args: any) => unknown | Promise<unknown>;
|
|
420
|
+
}
|
|
421
|
+
/** Connected apps: whether the person with a linked identity approved this application. */
|
|
422
|
+
export interface Connection {
|
|
423
|
+
linked: boolean;
|
|
424
|
+
authorized: boolean;
|
|
425
|
+
scopes?: string[];
|
|
426
|
+
authorize_url?: string;
|
|
427
|
+
link_url?: string;
|
|
428
|
+
}
|
|
429
|
+
export interface SaveFile {
|
|
430
|
+
name?: string;
|
|
431
|
+
media_type?: string;
|
|
432
|
+
data: string;
|
|
433
|
+
}
|
|
434
|
+
export interface SaveMessage {
|
|
435
|
+
role: "user" | "assistant";
|
|
436
|
+
name?: string;
|
|
437
|
+
text: string;
|
|
438
|
+
url?: string;
|
|
439
|
+
}
|
|
440
|
+
export interface WorkspaceSaveParams {
|
|
441
|
+
discord_id: string | number;
|
|
442
|
+
kind: "message" | "conversation" | "image" | "file";
|
|
443
|
+
title?: string;
|
|
444
|
+
text?: string;
|
|
445
|
+
messages?: SaveMessage[];
|
|
446
|
+
as_chat?: boolean;
|
|
447
|
+
files?: SaveFile[];
|
|
448
|
+
url?: string;
|
|
449
|
+
source?: Record<string, unknown>;
|
|
450
|
+
}
|
|
451
|
+
export interface WorkspaceSave {
|
|
452
|
+
saved: {
|
|
453
|
+
type: "reference" | "chat" | "file";
|
|
454
|
+
id: string;
|
|
455
|
+
}[];
|
|
456
|
+
url: string;
|
|
457
|
+
}
|
|
458
|
+
/** `executes`: the request's work may already be done when it fails (a run), so it is retried only when refused before starting. */
|
|
350
459
|
export interface RequestOptions {
|
|
351
460
|
form?: FormData;
|
|
352
461
|
query?: Record<string, string | number | undefined>;
|
|
353
462
|
raw?: boolean;
|
|
463
|
+
executes?: boolean;
|
|
354
464
|
}
|
|
355
465
|
/** Bytes from the API (speech audio, file downloads). */
|
|
356
466
|
export declare class Binary {
|
|
@@ -397,6 +507,8 @@ export declare class Stream<T> implements AsyncIterable<T> {
|
|
|
397
507
|
usage: Usage | undefined;
|
|
398
508
|
/** Responses streams: the final Response once response.completed arrives. */
|
|
399
509
|
response: ResponseObject | undefined;
|
|
510
|
+
/** Run streams: the completed run once run.completed arrives. */
|
|
511
|
+
run: Run | undefined;
|
|
400
512
|
readonly requestId: string;
|
|
401
513
|
constructor(http: Response, controller: AbortController, onDone?: () => void);
|
|
402
514
|
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
@@ -415,6 +527,27 @@ export declare class Dyadic {
|
|
|
415
527
|
/** Low-level request: JSON (or multipart `options.form`) in; JSON, text, Binary (`options.raw`) or a Stream out,
|
|
416
528
|
* with retries for busy or failing servers. */
|
|
417
529
|
request<T>(method: string, path: string, body?: unknown, stream?: boolean, options?: RequestOptions): Promise<T>;
|
|
530
|
+
/** Dyadic's own endpoints sit beside the OpenAI-compatible base: .../api/openai/v1 -> .../api/v1. */
|
|
531
|
+
platformURL(path: string): string;
|
|
532
|
+
/** An agent answers a conversation: one of the account's agents by id ("iris") or your own agent inline,
|
|
533
|
+
* with Dyadic's built-in tools and your own functions. A run that calls your functions ends with status
|
|
534
|
+
* "requires_action": run `function_calls`, append `messages` and your results, and run again (or use `run`). */
|
|
535
|
+
readonly runs: {
|
|
536
|
+
create: {
|
|
537
|
+
(params: RunParams & {
|
|
538
|
+
stream: true;
|
|
539
|
+
}): Promise<Stream<RunEvent>>;
|
|
540
|
+
(params: RunParams & {
|
|
541
|
+
stream?: false;
|
|
542
|
+
}): Promise<Run>;
|
|
543
|
+
};
|
|
544
|
+
/** Run to completion, calling your functions whenever the agent asks. The result's `messages` hold
|
|
545
|
+
* everything the run added, ready to append to your conversation. */
|
|
546
|
+
run: (params: Omit<RunParams, "functions" | "stream"> & {
|
|
547
|
+
functions?: Record<string, FunctionHandler>;
|
|
548
|
+
maxRounds?: number;
|
|
549
|
+
}) => Promise<Run>;
|
|
550
|
+
};
|
|
418
551
|
readonly models: {
|
|
419
552
|
list: () => Promise<{
|
|
420
553
|
object: "list";
|
|
@@ -529,6 +662,18 @@ export declare class Dyadic {
|
|
|
529
662
|
retrieve: (id: string) => Promise<Batch>;
|
|
530
663
|
cancel: (id: string) => Promise<Batch>;
|
|
531
664
|
};
|
|
665
|
+
/** Connected apps. Show the person `authorize_url` when `authorized` is false. */
|
|
666
|
+
readonly connections: {
|
|
667
|
+
discord: (discordId: string | number) => Promise<Connection>;
|
|
668
|
+
};
|
|
669
|
+
/** Save into the Workspace of the person with this Discord account, once they approved this application.
|
|
670
|
+
* Not approved yet throws DyadicError with code "authorization_required" (`error.body.authorize_url`);
|
|
671
|
+
* no Dyadic account with that Discord account: "account_not_linked". */
|
|
672
|
+
readonly workspace: {
|
|
673
|
+
saves: {
|
|
674
|
+
create: ({ discord_id, ...fields }: WorkspaceSaveParams) => Promise<WorkspaceSave>;
|
|
675
|
+
};
|
|
676
|
+
};
|
|
532
677
|
/** One question, one answer: the reply's text. */
|
|
533
678
|
ask(prompt: string, options?: Omit<ChatCompletionParams, "messages" | "model" | "stream"> & {
|
|
534
679
|
model?: Model;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The official TypeScript SDK for the Dyadic API: Iris
|
|
2
2
|
// through the OpenAI-compatible endpoints at https://dyadic.app/api/openai/v1.
|
|
3
|
-
export const VERSION = "1.
|
|
3
|
+
export const VERSION = "1.1.0";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://dyadic.app/api/openai/v1";
|
|
5
5
|
/** Bytes from the API (speech audio, file downloads). */
|
|
6
6
|
export class Binary {
|
|
@@ -90,12 +90,18 @@ export class Stream {
|
|
|
90
90
|
}
|
|
91
91
|
if (chunk.usage)
|
|
92
92
|
this.usage = chunk.usage;
|
|
93
|
+
if (chunk.type === "run.completed") {
|
|
94
|
+
this.run = chunk;
|
|
95
|
+
yield chunk;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
93
98
|
if ((chunk.type === "response.completed" || chunk.type === "response.incomplete") && chunk.response) {
|
|
94
99
|
this.response = chunk.response;
|
|
95
100
|
this.usage = chunk.response.usage ?? undefined;
|
|
96
101
|
}
|
|
97
102
|
const choice = chunk.choices?.[0];
|
|
98
|
-
this.text += chunk.type === "response.output_text.delta" ? chunk.delta ?? "" :
|
|
103
|
+
this.text += chunk.type === "response.output_text.delta" ? chunk.delta ?? "" : chunk.type === "delta" ? chunk.text ?? ""
|
|
104
|
+
: choice?.delta?.content ?? choice?.text ?? "";
|
|
99
105
|
yield chunk;
|
|
100
106
|
}
|
|
101
107
|
}
|
|
@@ -110,9 +116,46 @@ export class Stream {
|
|
|
110
116
|
abort() { this.controller.abort(); }
|
|
111
117
|
}
|
|
112
118
|
const RETRY = new Set([408, 409, 429, 500, 502, 503, 504]);
|
|
119
|
+
// A run may have executed (and been billed) by the time it fails.
|
|
120
|
+
const RUN_RETRY = new Set([429]);
|
|
113
121
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
114
122
|
export class Dyadic {
|
|
115
123
|
constructor(options = {}) {
|
|
124
|
+
/** An agent answers a conversation: one of the account's agents by id ("iris") or your own agent inline,
|
|
125
|
+
* with Dyadic's built-in tools and your own functions. A run that calls your functions ends with status
|
|
126
|
+
* "requires_action": run `function_calls`, append `messages` and your results, and run again (or use `run`). */
|
|
127
|
+
this.runs = {
|
|
128
|
+
create: ((params) => this.request("POST", this.platformURL("/runs"), params, Boolean(params.stream), { executes: true })),
|
|
129
|
+
/** Run to completion, calling your functions whenever the agent asks. The result's `messages` hold
|
|
130
|
+
* everything the run added, ready to append to your conversation. */
|
|
131
|
+
run: async (params) => {
|
|
132
|
+
const { functions = {}, maxRounds = 8, ...rest } = params;
|
|
133
|
+
const schemas = Object.entries(functions).map(([name, f]) => ({ type: "function", function: { name, description: f.description ?? "", parameters: f.parameters ?? { type: "object", properties: {} } } }));
|
|
134
|
+
const conversation = [...rest.messages];
|
|
135
|
+
const added = [];
|
|
136
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
137
|
+
const result = await this.runs.create({ ...rest, messages: conversation, functions: schemas.length ? schemas : undefined, stream: false });
|
|
138
|
+
if (result.status !== "requires_action")
|
|
139
|
+
return { ...result, messages: [...added, { role: "assistant", content: result.output_text }] };
|
|
140
|
+
const step = [...result.messages];
|
|
141
|
+
for (const call of result.function_calls) {
|
|
142
|
+
let args = {};
|
|
143
|
+
try {
|
|
144
|
+
args = JSON.parse(call.function.arguments || "{}");
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
args = {};
|
|
148
|
+
}
|
|
149
|
+
const fn = functions[call.function.name];
|
|
150
|
+
const output = fn ? await fn.handler(args) : { error: "unknown function" };
|
|
151
|
+
step.push({ role: "tool", tool_call_id: call.id, content: typeof output === "string" ? output : JSON.stringify(output) });
|
|
152
|
+
}
|
|
153
|
+
conversation.push(...step);
|
|
154
|
+
added.push(...step);
|
|
155
|
+
}
|
|
156
|
+
throw new DyadicError(`The run still wanted functions after ${maxRounds} rounds.`);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
116
159
|
this.models = {
|
|
117
160
|
list: () => this.request("GET", "/models"),
|
|
118
161
|
retrieve: (model) => this.request("GET", `/models/${model}`),
|
|
@@ -171,6 +214,18 @@ export class Dyadic {
|
|
|
171
214
|
retrieve: (id) => this.request("GET", `/batches/${id}`),
|
|
172
215
|
cancel: (id) => this.request("POST", `/batches/${id}/cancel`),
|
|
173
216
|
};
|
|
217
|
+
/** Connected apps. Show the person `authorize_url` when `authorized` is false. */
|
|
218
|
+
this.connections = {
|
|
219
|
+
discord: (discordId) => this.request("GET", this.platformURL(`/connections/discord/${discordId}`)),
|
|
220
|
+
};
|
|
221
|
+
/** Save into the Workspace of the person with this Discord account, once they approved this application.
|
|
222
|
+
* Not approved yet throws DyadicError with code "authorization_required" (`error.body.authorize_url`);
|
|
223
|
+
* no Dyadic account with that Discord account: "account_not_linked". */
|
|
224
|
+
this.workspace = {
|
|
225
|
+
saves: {
|
|
226
|
+
create: ({ discord_id, ...fields }) => this.request("POST", this.platformURL("/workspace/saves"), { user: { discord_id: String(discord_id) }, ...fields }, false, { executes: true }),
|
|
227
|
+
},
|
|
228
|
+
};
|
|
174
229
|
// process.env where it exists (Node, Bun, Deno with compat); browsers pass apiKey.
|
|
175
230
|
const env = globalThis.process?.env ?? {};
|
|
176
231
|
this.apiKey = options.apiKey ?? env.DYADIC_API_KEY ?? "";
|
|
@@ -189,7 +244,7 @@ export class Dyadic {
|
|
|
189
244
|
if (body !== undefined && !options.form)
|
|
190
245
|
headers["Content-Type"] = "application/json";
|
|
191
246
|
const query = Object.entries(options.query ?? {}).filter(([, v]) => v !== undefined).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
192
|
-
const url = `${this.baseURL
|
|
247
|
+
const url = `${path.startsWith("http") ? path : this.baseURL + path}${query ? `?${query}` : ""}`;
|
|
193
248
|
const payload = options.form ?? (body === undefined ? undefined : JSON.stringify(body));
|
|
194
249
|
if (this.appName)
|
|
195
250
|
headers["X-Title"] = this.appName;
|
|
@@ -203,7 +258,7 @@ export class Dyadic {
|
|
|
203
258
|
}
|
|
204
259
|
catch (error) {
|
|
205
260
|
clearTimeout(timer);
|
|
206
|
-
if (attempt < this.maxRetries) {
|
|
261
|
+
if (attempt < this.maxRetries && !options.executes) {
|
|
207
262
|
await sleep(500 * 2 ** attempt);
|
|
208
263
|
continue;
|
|
209
264
|
}
|
|
@@ -224,7 +279,7 @@ export class Dyadic {
|
|
|
224
279
|
clearTimeout(timer);
|
|
225
280
|
const data = await response.json().catch(() => ({ error: { message: `HTTP ${response.status}` } }));
|
|
226
281
|
const error = data?.error ?? {};
|
|
227
|
-
if (RETRY.has(response.status) && attempt < this.maxRetries && error.type !== "insufficient_quota") {
|
|
282
|
+
if ((options.executes ? RUN_RETRY : RETRY).has(response.status) && attempt < this.maxRetries && error.type !== "insufficient_quota") {
|
|
228
283
|
const after = Number(response.headers.get("retry-after"));
|
|
229
284
|
await sleep(Number.isFinite(after) && after > 0 ? Math.min(after, 30) * 1000 : 500 * 2 ** attempt);
|
|
230
285
|
continue;
|
|
@@ -233,6 +288,8 @@ export class Dyadic {
|
|
|
233
288
|
requestId: response.headers.get("x-request-id") ?? "", body: data });
|
|
234
289
|
}
|
|
235
290
|
}
|
|
291
|
+
/** Dyadic's own endpoints sit beside the OpenAI-compatible base: .../api/openai/v1 -> .../api/v1. */
|
|
292
|
+
platformURL(path) { return this.baseURL.replace(/\/openai(\/v1)?$/, "/v1") + path; }
|
|
236
293
|
/** One question, one answer: the reply's text. */
|
|
237
294
|
async ask(prompt, options = {}) {
|
|
238
295
|
const { model = "iris", system, ...params } = options;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dyadic",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The official TypeScript and JavaScript SDK for the Dyadic API
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "The official TypeScript and JavaScript SDK for the Dyadic API: runs with your own agents and functions, plus the OpenAI-compatible API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|