@oaiz/sdk 0.0.0-stage → 0.1.4128
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/LICENSE +21 -0
- package/README.md +484 -2
- package/dist/index.d.ts +2669 -0
- package/dist/index.js +3706 -0
- package/dist/test.d.mts +63 -0
- package/dist/test.mjs +2495 -0
- package/package.json +61 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OAIZ AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,485 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @oaiz/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/oaiz-io/oaiz-sdk/actions/workflows/ci.yaml)
|
|
4
|
+
|
|
5
|
+
The TypeScript client for apps built on [OAIZ](https://oaiz.io). It reads and writes the rows of
|
|
6
|
+
an OAIZ database, runs your integrations and OAIZ's built-in tools, starts workflows, publishes
|
|
7
|
+
events, calls a model, creates embeddings, and chats with an OAIZ agent, in the browser as a
|
|
8
|
+
signed-in member of your organisation or on your server with an API key.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @oaiz/sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## In the browser, in an OAIZ app
|
|
15
|
+
|
|
16
|
+
Give the client your app ID. It signs in a member of your organisation, and every request then runs
|
|
17
|
+
as that member, with their access. Where the IDs come from, and where sign-in returns, is under
|
|
18
|
+
[Setup](#setup).
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// src/oaiz.ts
|
|
22
|
+
import { createClient } from "@oaiz/sdk";
|
|
23
|
+
|
|
24
|
+
export const oaiz = createClient({
|
|
25
|
+
url: "https://api.oaiz.io",
|
|
26
|
+
database: "<database id>",
|
|
27
|
+
app: "<app id>",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export async function openTodos() {
|
|
31
|
+
// Every request needs a signed-in member; without one OAIZ answers 401.
|
|
32
|
+
const { data: session } = await oaiz.auth.getSession();
|
|
33
|
+
if (session === null) {
|
|
34
|
+
// The browser leaves for OAIZ here and comes back to this page, signed in.
|
|
35
|
+
const { error } = await oaiz.auth.signIn();
|
|
36
|
+
if (error) throw new Error(error.message);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const { data, error } = await oaiz.from("todos").select("id, title").eq("done", false);
|
|
40
|
+
if (error) throw new Error(error.message);
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// src/session.ts
|
|
47
|
+
import { oaiz } from "./oaiz";
|
|
48
|
+
|
|
49
|
+
export async function whoIsSignedIn() {
|
|
50
|
+
const { data: session } = await oaiz.auth.getSession();
|
|
51
|
+
if (session === null) return null;
|
|
52
|
+
const { data: user } = await oaiz.auth.getUser();
|
|
53
|
+
return user?.name ?? user?.email ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function signOut() {
|
|
57
|
+
await oaiz.auth.signOut();
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The session lives in the tab's `sessionStorage`, and the client renews it before it expires.
|
|
62
|
+
|
|
63
|
+
## On the server, with an API key
|
|
64
|
+
|
|
65
|
+
Give the client an API key instead of an app ID. Keep the key on the server; never ship it to a
|
|
66
|
+
browser. This client has no `auth` and no `chat`; everything else on this page works with both.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// server/oaiz.ts
|
|
70
|
+
import { createClient } from "@oaiz/sdk";
|
|
71
|
+
|
|
72
|
+
export const oaiz = createClient({
|
|
73
|
+
url: "https://api.oaiz.io",
|
|
74
|
+
database: "<database id>",
|
|
75
|
+
key: process.env.OAIZ_API_KEY ?? "",
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Query a database
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// src/todos.ts
|
|
83
|
+
import { oaiz } from "./oaiz";
|
|
84
|
+
|
|
85
|
+
export async function addTodo(title: string) {
|
|
86
|
+
return oaiz.from("todos").insert({ title, done: false }).select("id, title").single();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function finishTodo(id: string) {
|
|
90
|
+
return oaiz.from("todos").update({ done: true }).eq("id", id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function latestTodos() {
|
|
94
|
+
return oaiz.from("todos").select("id, title, done").order("created_at", { ascending: false }).limit(20);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`upsert` and `delete` work the same way. A database view only reads.
|
|
99
|
+
|
|
100
|
+
`oaiz db types <database id> -o src/databases/<database id>.ts` writes a `Database` type for your
|
|
101
|
+
tables. Pass it as `createClient<Database>({ ... })` to check table names, columns and values as
|
|
102
|
+
you type.
|
|
103
|
+
|
|
104
|
+
`rpc` calls a function you created in the database with `create function`, with its arguments by
|
|
105
|
+
name. See [queries](https://oaiz.io/docs/databases/queries#rpc).
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// src/functions.ts
|
|
109
|
+
import { oaiz } from "./oaiz";
|
|
110
|
+
|
|
111
|
+
export async function openCount() {
|
|
112
|
+
const { data, error } = await oaiz.rpc("count_open_todos", { owner_id: "<member id>" });
|
|
113
|
+
return error ? 0 : data;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Ask for `count` to get the number of matching rows:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// server/report.ts
|
|
121
|
+
import { oaiz } from "./oaiz.js";
|
|
122
|
+
|
|
123
|
+
export async function doneCount() {
|
|
124
|
+
const { count, error } = await oaiz.from("todos").select("id", { count: "exact" }).eq("done", true);
|
|
125
|
+
return error ? null : count;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Use your integrations
|
|
130
|
+
|
|
131
|
+
Every integration you connect in OAIZ (Gmail, Slack, HubSpot, ...) is a set of tools. `tools.run`
|
|
132
|
+
runs one by `<integration id>:<action>`, with the action's arguments. Reads and writes are separate
|
|
133
|
+
switches: `executeIntegrationQuery` lets the call read from the provider and
|
|
134
|
+
`executeIntegrationCommand` lets it write. A switch that is off makes the call return the
|
|
135
|
+
provider's sample output instead, so a script cannot write by accident.
|
|
136
|
+
|
|
137
|
+
On the server, read the latest unread emails, have a model write a reply, and save it as a draft.
|
|
138
|
+
For a longer job, start a workflow instead (see [Run a workflow](#run-a-workflow)).
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// server/inbox.ts
|
|
142
|
+
import { oaiz } from "./oaiz.js";
|
|
143
|
+
|
|
144
|
+
const gmail = "<gmail integration id>";
|
|
145
|
+
|
|
146
|
+
export async function unreadEmails() {
|
|
147
|
+
const { data, error } = await oaiz.tools.run(`${gmail}:search_emails`, {
|
|
148
|
+
arguments: { query: "is:unread newer_than:1d", limit: 5 },
|
|
149
|
+
executeIntegrationQuery: true,
|
|
150
|
+
executeIntegrationCommand: false,
|
|
151
|
+
});
|
|
152
|
+
if (error) throw new Error(error.message);
|
|
153
|
+
return data.result?.emails;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function writeReply(email: unknown) {
|
|
157
|
+
const { data, error } = await oaiz.llm.call({
|
|
158
|
+
model: "openai/gpt-6-luna",
|
|
159
|
+
system: "You answer customer email for Acme. Write only the reply, brief and concrete.",
|
|
160
|
+
input: JSON.stringify(email),
|
|
161
|
+
});
|
|
162
|
+
if (error) throw new Error(error.message);
|
|
163
|
+
return data.text;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function saveDraft(to: string, subject: string, body: string) {
|
|
167
|
+
return oaiz.tools.run(`${gmail}:create_draft`, {
|
|
168
|
+
arguments: { to: [to], subject, body },
|
|
169
|
+
executeIntegrationQuery: true,
|
|
170
|
+
executeIntegrationCommand: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
In the browser, give the agent the Gmail tools and let it read and draft itself. A tool with
|
|
176
|
+
`requireApproval` does not run until the member approves the call: the turn ends with a
|
|
177
|
+
`tool.approvalRequired` event, and `chat.approve` or `chat.decline`, with the same agent settings,
|
|
178
|
+
continues it. The app reaches only the integration actions shared with it (see [Setup](#setup)).
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// src/inbox.ts
|
|
182
|
+
import { oaiz } from "./oaiz";
|
|
183
|
+
|
|
184
|
+
const gmail = "<gmail integration id>";
|
|
185
|
+
const agent = {
|
|
186
|
+
systemPrompt: "You answer customer email for Acme. Be brief and concrete.",
|
|
187
|
+
integrationTools: [
|
|
188
|
+
{ integrationUuid: gmail, actionType: "search_emails" },
|
|
189
|
+
{ integrationUuid: gmail, actionType: "create_draft", requireApproval: true },
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Returns the draft call that waits for the member, or null when the agent saved none.
|
|
194
|
+
export async function draftReplies() {
|
|
195
|
+
let waiting: { sessionId: string; toolCallId: string } | null = null;
|
|
196
|
+
for await (const { data: event, error } of oaiz.chat.send({ ...agent, content: "Draft replies to today's unread email." })) {
|
|
197
|
+
if (error) throw new Error(error.message);
|
|
198
|
+
if (event.type === "tool.approvalRequired") {
|
|
199
|
+
waiting = { sessionId: event.sessionId ?? "", toolCallId: event.toolApprovalInfo?.toolCallId ?? "" };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return waiting;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function decide(call: { sessionId: string; toolCallId: string }, approved: boolean) {
|
|
206
|
+
const turn = approved ? oaiz.chat.approve({ ...agent, ...call }) : oaiz.chat.decline({ ...agent, ...call });
|
|
207
|
+
for await (const { error } of turn) {
|
|
208
|
+
if (error) throw new Error(error.message);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Built-in OAIZ tools run the same way, by name. In the browser, a chat turn names them in
|
|
214
|
+
`nativeTools`, and `chat.tools()` lists the built-in tools and integration actions the app may use.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// server/places.ts
|
|
218
|
+
import { oaiz } from "./oaiz.js";
|
|
219
|
+
|
|
220
|
+
export async function coffeeNearby() {
|
|
221
|
+
const { data, error } = await oaiz.tools.run("google_maps_search", {
|
|
222
|
+
arguments: { query: "coffee near Stureplan, Stockholm", limit: 3 },
|
|
223
|
+
executeIntegrationQuery: true,
|
|
224
|
+
executeIntegrationCommand: false,
|
|
225
|
+
});
|
|
226
|
+
return error ? null : data.result;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Run a workflow
|
|
231
|
+
|
|
232
|
+
`runs.start` starts a workflow's job from an event you describe; the event's `properties` are the
|
|
233
|
+
workflow's input. It yields the run's progress until the run ends or waits, for example for an
|
|
234
|
+
approval. The first frame is `root_start` with the run's ID in `actionExecutionLogUuid`. The last
|
|
235
|
+
is `terminal`, or `waiting` when the run waits, and its `terminalReason` says why: `success`,
|
|
236
|
+
`failure`, `awaiting-approval` and so on. In the browser, the app starts only the workflows shared
|
|
237
|
+
with it. A workflow is a set of jobs; `runs.start` takes the ID of one of its jobs
|
|
238
|
+
(`oaiz workflow job list <workflow id>`).
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// server/runs.ts
|
|
242
|
+
import { oaiz } from "./oaiz.js";
|
|
243
|
+
|
|
244
|
+
export async function weeklyReport(week: number) {
|
|
245
|
+
const run = oaiz.runs.start("<job id>", {
|
|
246
|
+
event: { eventType: { provider: "custom", name: "report-requested" }, properties: { week } },
|
|
247
|
+
});
|
|
248
|
+
let reason = "";
|
|
249
|
+
for await (const { data: progress, error } of run) {
|
|
250
|
+
if (error) throw new Error(error.message);
|
|
251
|
+
reason = progress.event.terminalReason ?? reason;
|
|
252
|
+
}
|
|
253
|
+
return reason;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Reads a run again later, with its logs and results.
|
|
257
|
+
export async function readRun(runId: string) {
|
|
258
|
+
const { data, error } = await oaiz.runs.get(runId);
|
|
259
|
+
return error ? null : data.status;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Every frame carries an `after`. When the stream breaks, `runs.follow(<run id>, { after })` reads
|
|
264
|
+
on from the last frame you kept; following never starts the run again. `runs.list` lists runs, for
|
|
265
|
+
example the ones an event started, with `triggeringEventUuid`.
|
|
266
|
+
|
|
267
|
+
## Publish an event
|
|
268
|
+
|
|
269
|
+
An event's type is a `provider` and a `name`, each made of letters, digits and hyphens. Use the
|
|
270
|
+
provider `custom` for your own events; `storage`, `schedule` and the names of integration providers
|
|
271
|
+
such as `gmail` are reserved. Workflows that listen for the type start on it.
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
// server/events.ts
|
|
275
|
+
import { oaiz } from "./oaiz.js";
|
|
276
|
+
|
|
277
|
+
export async function orderCreated(orderId: string, total: number) {
|
|
278
|
+
const { data, error } = await oaiz.events.publish({
|
|
279
|
+
eventType: { provider: "custom", name: "order-created" },
|
|
280
|
+
properties: { orderId, total },
|
|
281
|
+
});
|
|
282
|
+
if (error) throw new Error(error.message);
|
|
283
|
+
return data.uuid;
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Call a model
|
|
288
|
+
|
|
289
|
+
`llm.call` asks a model for one answer to `input`, under an optional `system` instruction, and
|
|
290
|
+
returns the `text`, the `model` that answered and the `usage` in tokens. The call runs as the key's
|
|
291
|
+
or the signed-in member's organisation, within its budget, on a model the organisation permits in
|
|
292
|
+
both chat and workflows; a refusal is `error.code` `budget-exceeded` or `llm-model-not-permitted`.
|
|
293
|
+
An `input` longer than the model's context answers `llm-call-context-length`. The call waits up to
|
|
294
|
+
five minutes for the answer.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
// server/summary.ts
|
|
298
|
+
import { oaiz } from "./oaiz.js";
|
|
299
|
+
|
|
300
|
+
export async function summarise(text: string) {
|
|
301
|
+
const { data, error } = await oaiz.llm.call({
|
|
302
|
+
model: "openai/gpt-6-luna",
|
|
303
|
+
system: "Summarise the text in one sentence.",
|
|
304
|
+
input: text,
|
|
305
|
+
});
|
|
306
|
+
if (error) throw new Error(error.message);
|
|
307
|
+
return data.text;
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Create embeddings
|
|
312
|
+
|
|
313
|
+
`embeddings.create` turns each text into a vector, with `text-embedding-3-small` or
|
|
314
|
+
`text-embedding-3-large`; `dimensions` asks for shorter vectors. Vectors from different models, or
|
|
315
|
+
from one model at different dimensions, are not comparable, so store the model beside each vector.
|
|
316
|
+
`JSON.stringify(vector)` is the text a pgvector column takes.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
// server/embeddings.ts
|
|
320
|
+
import { oaiz } from "./oaiz.js";
|
|
321
|
+
|
|
322
|
+
export async function saveArticle(title: string, text: string) {
|
|
323
|
+
const { data, error } = await oaiz.embeddings.create({ model: "text-embedding-3-small", input: [text] });
|
|
324
|
+
if (error) throw new Error(error.message);
|
|
325
|
+
// data: { model, dimensions, embeddings, inputTokens }
|
|
326
|
+
return oaiz.from("articles").insert({ title, model: data.model, embedding: JSON.stringify(data.embeddings[0]) });
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Chat with an agent
|
|
331
|
+
|
|
332
|
+
In the browser, as the signed-in member. `chat.send` streams the agent's answer as events.
|
|
333
|
+
`message.delta` events carry the text; `session.created` carries the ID of a new chat, which you
|
|
334
|
+
pass as `sessionId` to continue it.
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
// src/chat.ts
|
|
338
|
+
import { oaiz } from "./oaiz";
|
|
339
|
+
|
|
340
|
+
export async function ask(question: string, onText: (text: string) => void) {
|
|
341
|
+
let answer = "";
|
|
342
|
+
const turn = oaiz.chat.send({ content: question, systemPrompt: "You help the team plan their week." });
|
|
343
|
+
for await (const { data: event, error } of turn) {
|
|
344
|
+
if (error) throw new Error(error.message);
|
|
345
|
+
if (event.type === "message.delta") {
|
|
346
|
+
answer += event.delta?.content ?? "";
|
|
347
|
+
onText(answer);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return answer;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Test mode: `@oaiz/sdk/test`
|
|
355
|
+
|
|
356
|
+
Test mode runs your app against local copies of your databases, with no network. You need an OAIZ
|
|
357
|
+
account once, to write each database's schema file. The `oaizTest` Vite plugin serves sign-in, rows
|
|
358
|
+
and database functions from [PGlite](https://pglite.dev) while the dev server runs as
|
|
359
|
+
`vite --mode oaiz-test`. It runs in Node only.
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
npm install --save-dev vite @electric-sql/pglite @electric-sql/pglite-pgvector
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
// vite.config.ts
|
|
367
|
+
import { oaizTest } from "@oaiz/sdk/test";
|
|
368
|
+
import { defineConfig } from "vite";
|
|
369
|
+
|
|
370
|
+
export default defineConfig({ plugins: [oaizTest()] });
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The plugin reads your app's `tests/` folder:
|
|
374
|
+
|
|
375
|
+
- `tests/databases/<database id>.sql`, the database's schema. Write it with
|
|
376
|
+
`oaiz db test-schema <database id> -o tests/databases/<database id>.sql`; a hand-written
|
|
377
|
+
`create table` file does not work, because the file also sets up the roles test mode signs in
|
|
378
|
+
with.
|
|
379
|
+
- `tests/databases/<database id>.seed.sql` (optional), SQL that inserts the starting rows.
|
|
380
|
+
- `tests/members.json`, the people who can sign in:
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"teams": [{ "id": "7e570000-0000-4000-8000-000000000101", "name": "Sales" }],
|
|
385
|
+
"members": [
|
|
386
|
+
{
|
|
387
|
+
"id": "7e570000-0000-4000-8000-000000000001",
|
|
388
|
+
"name": "Ada Lovelace",
|
|
389
|
+
"email": "ada@example.test",
|
|
390
|
+
"teams": ["7e570000-0000-4000-8000-000000000101"],
|
|
391
|
+
"role": "administrator"
|
|
392
|
+
}
|
|
393
|
+
],
|
|
394
|
+
"grants": { "<database id>": "view" }
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Only `members` is required. Member and team IDs are UUIDs, as in OAIZ. A member's `role` is
|
|
399
|
+
`member` (the default) or `administrator`, and its `teams` lists IDs from the top-level `teams`.
|
|
400
|
+
`grants` sets the app's access per database; a database left out gets `edit`.
|
|
401
|
+
|
|
402
|
+
The plugin reads the folder on the first request, so after you change a file in `tests/`, restart
|
|
403
|
+
the dev server.
|
|
404
|
+
|
|
405
|
+
In test mode, set the client's `url` to `` `${window.location.origin}/__oaiz-test` ``. `signIn()`
|
|
406
|
+
then opens a local page where you pick a member. Chat, tools, model calls, embeddings, events
|
|
407
|
+
and workflow runs are not supported there and answer `501` with `error.code` `test-mode-unsupported`.
|
|
408
|
+
When a file in `tests/` does not load, the request answers `500` with `error.code`
|
|
409
|
+
`test-mode-setup` and the reason in `error.message`, and the dev server prints the reason.
|
|
410
|
+
|
|
411
|
+
## Setup
|
|
412
|
+
|
|
413
|
+
Each ID comes from the `oaiz` command-line tool or the OAIZ web app.
|
|
414
|
+
|
|
415
|
+
1. **An OAIZ account.** Install the CLI ([install](https://oaiz.io/docs/cli#install)) and sign in
|
|
416
|
+
with `oaiz login` ([authentication](https://oaiz.io/docs/cli#authentication)).
|
|
417
|
+
2. **A database ID.** `oaiz db create --name todos` creates a database and prints its ID. See
|
|
418
|
+
[databases](https://oaiz.io/docs/databases) for tables and migrations.
|
|
419
|
+
3. **An app ID**, for code that runs in the browser. `oaiz view create "Todos"` creates the app
|
|
420
|
+
and prints its ID; the CLI calls an app a *view* and saves the ID as `viewId` in
|
|
421
|
+
`.oaiz/view.json`. Give the app access to the database with
|
|
422
|
+
`oaiz db access grant <database id> --subject-kind app --subject-id <app id> --capability edit`
|
|
423
|
+
(or `view` for read only).
|
|
424
|
+
4. **An API key**, for code that runs on your server only. Create one in the OAIZ web app under
|
|
425
|
+
Settings → API → API keys ([API keys](https://oaiz.io/docs/api-keys#create)).
|
|
426
|
+
|
|
427
|
+
**What the app reaches.** In the browser, the app reaches only what you share with it, and the
|
|
428
|
+
member only what they may use:
|
|
429
|
+
|
|
430
|
+
- integration actions:
|
|
431
|
+
`oaiz integration access grant <integration id> --subject-kind app --subject-id <app id> --capability edit --tool-scope selected --action-types search_emails,create_draft`;
|
|
432
|
+
- workflows: `oaiz workflow access grant <workflow id> --subject-kind app --subject-id <app id> --capability view`;
|
|
433
|
+
- built-in tools: `oaiz app tools set <app id> --body '{"tools":["google_maps_search"]}'`.
|
|
434
|
+
|
|
435
|
+
**Where sign-in returns.** `signIn()` sends the browser to OAIZ, and OAIZ sends it back to the
|
|
436
|
+
same page, signed in. That page must be served from one of these addresses:
|
|
437
|
+
|
|
438
|
+
- the app's published address, or an HTTPS address you add with
|
|
439
|
+
`oaiz app origins add <app id> --origin https://todos.example.com`; the member needs view
|
|
440
|
+
access to the app;
|
|
441
|
+
- your local development server or a draft's preview while you develop; the member needs edit
|
|
442
|
+
access to the app. On your local development server, OAIZ asks them to approve every sign-in.
|
|
443
|
+
|
|
444
|
+
## Errors never throw
|
|
445
|
+
|
|
446
|
+
Every call resolves to `{ data, error, count, status }`. On success `error` is `null`. On failure
|
|
447
|
+
`data` is `null` and `error` is `{ message, code, hints }`: `code` names the reason in a stable
|
|
448
|
+
form such as `auth-signed-out`, so compare `error.code` rather than `message`. `status` is the HTTP
|
|
449
|
+
status, or `0` when no answer arrived (offline, aborted). Auth calls resolve to `{ data, error }`
|
|
450
|
+
the same way. Streams (`chat.send`, `runs.start`) yield the same shape for each event. Only
|
|
451
|
+
`createClient` throws, when it gets both `key` and `app` or neither.
|
|
452
|
+
|
|
453
|
+
## Runtime support
|
|
454
|
+
|
|
455
|
+
Releases are tested on Node.js 26 and TypeScript 7.0. The package is ESM only.
|
|
456
|
+
|
|
457
|
+
**Tests**: every push to `main` and every pull request in the
|
|
458
|
+
[source repository](https://github.com/oaiz-io/oaiz-sdk) runs the unit and type tests, and
|
|
459
|
+
compiles every TypeScript example in this README, in
|
|
460
|
+
[CI](https://github.com/oaiz-io/oaiz-sdk/actions/workflows/ci.yaml). A release is published only
|
|
461
|
+
when the same tests pass.
|
|
462
|
+
|
|
463
|
+
- **Browsers**: current evergreen browsers, on a page served over HTTPS or from your own machine
|
|
464
|
+
while you develop, which browsers treat as a secure context. The app client needs `fetch`,
|
|
465
|
+
`crypto.randomUUID`, `crypto.getRandomValues`, `crypto.subtle`, `TextEncoder`, `btoa`,
|
|
466
|
+
`sessionStorage` and streams. Voice chat (`chat.voice`) also needs `RTCPeerConnection`,
|
|
467
|
+
`getUserMedia` and `AudioContext`.
|
|
468
|
+
- **Node.js** 22.12 or newer (`engines`).
|
|
469
|
+
- **Not tested**: Bun, Deno and edge runtimes. They may work, but no release checks them.
|
|
470
|
+
|
|
471
|
+
## Stability and changes
|
|
472
|
+
|
|
473
|
+
The package is versioned `0.x`: any release can change or remove part of the API, so pin the exact
|
|
474
|
+
version you test with (`npm install --save-exact @oaiz/sdk`). Each release is one commit and one
|
|
475
|
+
tag in the [source repository](https://github.com/oaiz-io/oaiz-sdk); compare two `sdk-v*` tags to
|
|
476
|
+
see what changed.
|
|
477
|
+
|
|
478
|
+
Report a bug at [github.com/oaiz-io/oaiz-sdk/issues](https://github.com/oaiz-io/oaiz-sdk/issues).
|
|
479
|
+
Report a vulnerability by email to security@oaiz.io, as
|
|
480
|
+
[SECURITY.md](https://github.com/oaiz-io/oaiz-sdk/blob/main/SECURITY.md) describes, not in a public
|
|
481
|
+
issue.
|
|
482
|
+
|
|
483
|
+
## Licence
|
|
484
|
+
|
|
485
|
+
MIT
|