@runappsai/sdk 0.3.0 → 0.4.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 +226 -95
- package/dist/activity-tracker.js +1 -1
- package/dist/browser-auth.d.ts +2 -1
- package/dist/browser-auth.d.ts.map +1 -1
- package/dist/browser-auth.js +61 -32
- package/dist/browser-auth.js.map +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/media.d.ts +2 -2
- package/dist/media.js +2 -2
- package/dist/sdk.umd.js +2 -2
- package/dist/sdk.umd.js.map +1 -1
- package/dist/transport.d.ts +2 -2
- package/package.json +4 -4
- package/src/activity-tracker.ts +1 -1
- package/src/browser-auth.ts +62 -31
- package/src/client.ts +4 -4
- package/src/index.ts +2 -0
- package/src/media.ts +2 -2
- package/src/transport.ts +2 -2
package/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
# RunApps SDK
|
|
1
|
+
# RunApps — SDK and platform docs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[RunApps](https://www.runapps.ai) is a platform for AI web apps. A creator publishes a static frontend; it runs on its own address, `<slug>.runapps.dev`; users sign in with one RunApps account and pay only for the AI they use. This package is the client for building on it: call any model (Claude, GPT, Gemini, DeepSeek, Qwen, MiniMax, GLM, Grok), generate images, audio and video, keep per-user files, and let the platform handle sign-in and billing.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
There are two ways to use it:
|
|
6
6
|
|
|
7
|
-
- **In an app you publish on RunApps
|
|
8
|
-
- **From your own code
|
|
7
|
+
- **In an app you publish on RunApps.** Users sign in with their RunApps account and pay for their own usage. You never hold a key. This is `authProvider: "runapps"`.
|
|
8
|
+
- **From your own code.** A server, script or CLI uses a personal API key (`rk_…`), billed to you.
|
|
9
|
+
|
|
10
|
+
Both use the base URL `https://www.runapps.ai` and the same OpenAI-compatible API under `/v1`.
|
|
11
|
+
|
|
12
|
+
**Contents:** [Install](#install) · [Build an app](#build-an-app) · [Publish it](#publish-it) · [From your own code](#from-your-own-code) · [Chat](#chat) · [Files](#files) · [Models](#models) · [Image, audio, video, embeddings](#image-audio-video-embeddings) · [HTTP API](#http-api) · [Errors](#errors) · [Reference](#reference)
|
|
13
|
+
|
|
14
|
+
---
|
|
9
15
|
|
|
10
16
|
## Install
|
|
11
17
|
|
|
12
18
|
```bash
|
|
13
|
-
npm
|
|
19
|
+
npm i @runappsai/sdk
|
|
14
20
|
```
|
|
15
21
|
|
|
16
|
-
Or
|
|
22
|
+
Or load it with a script tag, which defines the global `RunApps`:
|
|
17
23
|
|
|
18
24
|
```html
|
|
19
25
|
<script src="https://cdn.jsdelivr.net/npm/@runappsai/sdk/dist/sdk.umd.js"></script>
|
|
@@ -22,18 +28,20 @@ Or as a script tag:
|
|
|
22
28
|
</script>
|
|
23
29
|
```
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
No runtime dependencies. Runs in browsers, Node 18+, Deno and Bun.
|
|
32
|
+
|
|
33
|
+
## Build an app
|
|
26
34
|
|
|
27
35
|
### 1. Create the app, then develop against it
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
Create the app on the dashboard, under **Creator → Publish**. It gets its slug at once, as a draft visible only to you, before you upload anything. Pass the slug as `project` and run your dev server as usual:
|
|
30
38
|
|
|
31
39
|
```ts
|
|
32
40
|
import { RunApps } from "@runappsai/sdk";
|
|
33
41
|
|
|
34
42
|
const client = new RunApps({
|
|
35
|
-
authProvider: "runapps", // sign-in
|
|
36
|
-
project: "my-tool", // the draft's slug;
|
|
43
|
+
authProvider: "runapps", // sign-in and tokens are handled for you
|
|
44
|
+
project: "my-tool", // the draft's slug; it stays the same after you upload
|
|
37
45
|
});
|
|
38
46
|
|
|
39
47
|
const resp = await client.chat.create({
|
|
@@ -43,99 +51,91 @@ const resp = await client.chat.create({
|
|
|
43
51
|
console.log(resp.choices[0].message.content);
|
|
44
52
|
```
|
|
45
53
|
|
|
46
|
-
- Sign-in works from `http://localhost
|
|
47
|
-
- Calls are
|
|
48
|
-
- Files and private prompts already use the app's real
|
|
54
|
+
- Sign-in works from `http://localhost` on any port. The first call sends you to RunApps to sign in and back.
|
|
55
|
+
- Calls are charged to the signed-in user, which is you while developing.
|
|
56
|
+
- Files and private prompts already use the app's real space, so nothing moves when you upload.
|
|
49
57
|
|
|
50
|
-
When the app is ready, build it, zip
|
|
58
|
+
When the app is ready, build it, zip the output and upload it to the same app. See [Publish it](#publish-it).
|
|
51
59
|
|
|
52
|
-
### 2. Sign-in
|
|
60
|
+
### 2. Sign-in
|
|
53
61
|
|
|
54
62
|
```ts
|
|
55
|
-
await client.chat.create({ ... }); //
|
|
63
|
+
await client.chat.create({ ... }); // sends the user to sign in on first use
|
|
56
64
|
|
|
57
|
-
client.signIn(); //
|
|
58
|
-
client.signOut(); //
|
|
59
|
-
client.user; // { id, name }
|
|
60
|
-
client.auth?.onTokenChange(t => {}); //
|
|
65
|
+
client.signIn(); // send them now
|
|
66
|
+
client.signOut(); // forget the token, also across reloads
|
|
67
|
+
client.user; // { id, name } or null
|
|
68
|
+
client.auth?.onTokenChange(t => {}); // react to sign-in
|
|
61
69
|
client.auth?.hasFreshToken(); // avoid showing a "sign in" button needlessly
|
|
62
70
|
```
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
The SDK holds one token per user per app. It draws a small activity badge while it works; turn it off with `showIdentityBadge: false`, or move it with `badgePosition`.
|
|
65
73
|
|
|
66
|
-
`project` is optional on `*.runapps.dev
|
|
74
|
+
`project` is optional on `*.runapps.dev`, where the address already says which app it is. It is required on localhost and when the client runs on a personal key. Sign-in only works on the app's own `*.runapps.dev` address and on `http://localhost`. In Node, `authProvider: "runapps"` does nothing; use `apiKey`.
|
|
67
75
|
|
|
68
76
|
### 3. Private prompts
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
Everything in a frontend can be read in the browser. To keep a prompt private, ship it in the bundle where the platform reads it but never serves it. Put a `bundle.json` at the root of the zip that names your prompt files:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"prompts": {
|
|
83
|
+
"outline": "prompts/outline.md",
|
|
84
|
+
"critic": "prompts/critic.md"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Neither `bundle.json` nor the files it names can be downloaded from your app's address. Files under `.runapps/prompts/<id>.md` also work without an entry, because dot folders are never served. Call a prompt by id and fill its `{{variables}}`:
|
|
71
90
|
|
|
72
91
|
```ts
|
|
73
92
|
await client.chat.create({
|
|
74
93
|
model: "Claude Sonnet 4.6",
|
|
75
94
|
messages: [{ role: "user", content: "A detective in a lighthouse." }],
|
|
76
|
-
prompt: { id: "outline", vars: { genre: "mystery" } }, // {{genre}} in
|
|
95
|
+
prompt: { id: "outline", vars: { genre: "mystery" } }, // {{genre}} in outline.md
|
|
77
96
|
});
|
|
78
97
|
```
|
|
79
98
|
|
|
80
|
-
The
|
|
99
|
+
The platform adds the prompt as the system message and tells the model to keep it confidential. A reply that repeats 30 or more consecutive characters of it is withheld, with `finish_reason: "content_filter"`.
|
|
81
100
|
|
|
82
|
-
|
|
101
|
+
While developing locally, before a bundle exists, send the text inline as `prompt: { template: "…", vars }`. Only the app's creator may do this. Keep it out of production builds, for example behind `import.meta.env.DEV`.
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
## Publish it
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
// Write. body: Blob | File | ArrayBuffer | Uint8Array | ReadableStream | string
|
|
88
|
-
const obj = await client.files.put("assets/logo.png", pngBlob, { contentType: "image/png" });
|
|
89
|
-
await client.files.putString("notes.md", "# hello");
|
|
90
|
-
await client.files.putFromURL("cache/cat.jpg", "https://example.com/cat.jpg"); // gateway fetches it
|
|
105
|
+
Upload your build as a **zip** on the app's publish page:
|
|
91
106
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
- **Size.** Up to 50 MB.
|
|
108
|
+
- **Entry page.** The zip needs at least one HTML file. `index.html` is the default page, and you can pick another.
|
|
109
|
+
- **Asset paths.** Your app runs at the root of its own address, so absolute paths such as `/assets/app.js` work.
|
|
110
|
+
- **Client-side routing.** A request for a path without a file extension that doesn't exist, such as `/settings`, gets your entry page.
|
|
111
|
+
- **Hidden files.** Files and folders whose name starts with a dot, such as `.env` or `.git`, are never served.
|
|
96
112
|
|
|
97
|
-
|
|
98
|
-
const page = await client.files.list({ prefix: "assets/", glob: "**/*.png", limit: 100 });
|
|
99
|
-
// page.next_cursor → pass as cursor for the next page
|
|
113
|
+
You can upload a new zip at any time; it replaces the old one immediately.
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await client.files.deleteMany({ glob: "**/*.tmp" });
|
|
115
|
+
| Visibility | Who can open it | Listed in the library |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| Draft | Only you | No |
|
|
118
|
+
| Private | Anyone with the link | No |
|
|
119
|
+
| Public | Everyone | Yes, after review |
|
|
107
120
|
|
|
108
|
-
|
|
109
|
-
await client.files.batch([
|
|
110
|
-
{ op: "put_url", path: "a.jpg", src_url: "https://example.com/a.jpg" },
|
|
111
|
-
{ op: "copy", from: "a.jpg", to: "b.jpg" },
|
|
112
|
-
{ op: "del", path: "a.jpg" },
|
|
113
|
-
]);
|
|
114
|
-
```
|
|
121
|
+
Submitting an app as public sends it to review. If it is rejected, fix it and submit again.
|
|
115
122
|
|
|
116
|
-
|
|
123
|
+
### Earnings
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
const { value, etag } = await client.files.readJSON<{ score: number }>("state.json");
|
|
120
|
-
const next = { ...(value ?? { score: 0 }), score: (value?.score ?? 0) + 1 };
|
|
121
|
-
try {
|
|
122
|
-
await client.files.put("state.json", JSON.stringify(next), {
|
|
123
|
-
contentType: "application/json",
|
|
124
|
-
...(etag ? { ifMatch: etag } : { ifNoneMatch: true }), // first save: must not exist
|
|
125
|
-
});
|
|
126
|
-
} catch (e) {
|
|
127
|
-
if (e instanceof APIError && e.statusCode === 412) { /* re-read, merge, retry */ }
|
|
128
|
-
}
|
|
129
|
-
```
|
|
125
|
+
Set a **creator fee** of 0% to 50% on the publish page. On every AI call a user makes in your app, they pay the fee on top of the model charge. The whole fee goes to your **earnings balance**, and your own use of your app carries no fee. You can transfer earnings to your main balance or withdraw them to PayPal, with a $10 minimum and 3 to 5 business days for payouts.
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
### What your users see
|
|
128
|
+
|
|
129
|
+
Users find public apps in the library and open them at `<slug>.runapps.dev`. They top up a US-dollar balance by card or PayPal, or Alipay and WeChat Pay in the Chinese interface, and each call is charged at the model's price plus your fee. Your app's page shows the fee before they open it. Users can set a daily budget, and the files your app saves for them appear on their Storage page. Removing your app from their account deletes those files.
|
|
132
130
|
|
|
133
131
|
## From your own code
|
|
134
132
|
|
|
133
|
+
Create a key on the dashboard's **API Keys** page.
|
|
134
|
+
|
|
135
135
|
```ts
|
|
136
|
-
import { RunApps
|
|
136
|
+
import { RunApps } from "@runappsai/sdk";
|
|
137
137
|
|
|
138
|
-
const client = new RunApps({ apiKey: "rk_…" });
|
|
138
|
+
const client = new RunApps({ apiKey: "rk_…" });
|
|
139
139
|
|
|
140
140
|
const resp = await client.chat.create({
|
|
141
141
|
model: "Gemini 3 Flash",
|
|
@@ -144,23 +144,27 @@ const resp = await client.chat.create({
|
|
|
144
144
|
console.log(resp.usage.total_cost); // USD
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
**Files with a personal key** — a personal key has no app of its own, so name the app: `new RunApps({ apiKey, project: "my-tool" })`. You get your own files in that app, the same ones the app sees for you, provided you have added the app or created it.
|
|
150
|
-
|
|
151
|
-
**Any OpenAI-compatible SDK works too.** Point it at the platform with your `rk_…` key:
|
|
147
|
+
Any OpenAI-compatible client works too:
|
|
152
148
|
|
|
153
149
|
```python
|
|
154
150
|
from openai import OpenAI
|
|
151
|
+
|
|
155
152
|
client = OpenAI(api_key="rk_...", base_url="https://www.runapps.ai/v1")
|
|
156
153
|
client.chat.completions.create(model="Claude Sonnet 4.6", messages=[{"role": "user", "content": "Hello!"}])
|
|
157
154
|
```
|
|
158
155
|
|
|
159
|
-
|
|
156
|
+
```bash
|
|
157
|
+
curl https://www.runapps.ai/v1/chat/completions \
|
|
158
|
+
-H "Authorization: Bearer $RUNAPPS_API_KEY" \
|
|
159
|
+
-H "Content-Type: application/json" \
|
|
160
|
+
-d '{"model": "Claude Sonnet 4.6", "messages": [{"role": "user", "content": "Hello!"}]}'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Never put an `rk_…` key in a frontend; an app on RunApps uses `authProvider: "runapps"`. `baseURL` overrides the base URL in either mode.
|
|
160
164
|
|
|
161
165
|
## Chat
|
|
162
166
|
|
|
163
|
-
OpenAI
|
|
167
|
+
`POST /v1/chat/completions` follows the OpenAI format, streaming or not. If you leave out `model`, the user's default model is used, then the platform's. `usage.total_cost` is the charge in USD; when streaming, the last chunk carries `usage`.
|
|
164
168
|
|
|
165
169
|
```ts
|
|
166
170
|
for await (const chunk of client.chat.stream({ model: "Gemini 3 Flash", messages })) {
|
|
@@ -169,10 +173,10 @@ for await (const chunk of client.chat.stream({ model: "Gemini 3 Flash", messages
|
|
|
169
173
|
}
|
|
170
174
|
```
|
|
171
175
|
|
|
172
|
-
**Images, video and audio in messages
|
|
176
|
+
**Images, video and audio in messages.** Check that the model accepts them with `acceptsModality(model, "video")`; unsupported parts are rejected with a 400 before anything is charged.
|
|
173
177
|
|
|
174
178
|
```ts
|
|
175
|
-
import { userMessageParts, textPart,
|
|
179
|
+
import { userMessageParts, textPart, videoPart } from "@runappsai/sdk";
|
|
176
180
|
|
|
177
181
|
await client.chat.create({
|
|
178
182
|
model: "Gemini 3 Vision",
|
|
@@ -180,9 +184,11 @@ await client.chat.create({
|
|
|
180
184
|
});
|
|
181
185
|
```
|
|
182
186
|
|
|
183
|
-
**Your own tools
|
|
187
|
+
**Your own tools.** Pass `tools: [{ type: "function", function: { name, description, parameters } }]`, read `choices[0].message.tool_calls`, and answer with `toolResultMessage(toolCallId, output)` on the next turn.
|
|
188
|
+
|
|
189
|
+
### Server tools
|
|
184
190
|
|
|
185
|
-
|
|
191
|
+
The platform can search and read the web for the model, looping until it answers. Your code only sees the final reply.
|
|
186
192
|
|
|
187
193
|
```ts
|
|
188
194
|
import { ServerTools } from "@runappsai/sdk";
|
|
@@ -191,14 +197,74 @@ const resp = await client.chat.create({
|
|
|
191
197
|
model: "Claude Sonnet 4.6",
|
|
192
198
|
messages: [{ role: "user", content: "What is the latest stable Go version?" }],
|
|
193
199
|
server_tools: [ServerTools.WebSearch, ServerTools.WebFetch],
|
|
194
|
-
max_server_iterations: 5, // default 5,
|
|
200
|
+
max_server_iterations: 5, // default 5, at most 10
|
|
195
201
|
});
|
|
196
202
|
for (const t of resp.usage.tool_costs ?? []) console.log(t.name, t.count, t.cost);
|
|
197
203
|
```
|
|
198
204
|
|
|
199
|
-
|
|
205
|
+
| Tool | What it does | Price |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `web_search` | Searches the web | Per call |
|
|
208
|
+
| `web_fetch` | Reads a page as text | Free |
|
|
209
|
+
| `twitter_search` | Searches posts on X | Per call |
|
|
210
|
+
|
|
211
|
+
The tokens of every round and each tool call are billed together, and `usage.total_cost` includes both. Server tools combine with your own `tools`; if the model calls one of yours, the loop stops and the call is returned to you. With `stream: true` the tool rounds run silently and the final answer streams.
|
|
212
|
+
|
|
213
|
+
## Files
|
|
214
|
+
|
|
215
|
+
Each app has a file space for each signed-in user. Paths look like POSIX paths, and every file has a public `url` you can use in `<img src>`.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// Write. body: Blob | File | ArrayBuffer | Uint8Array | ReadableStream | string
|
|
219
|
+
const obj = await client.files.put("assets/logo.png", pngBlob, { contentType: "image/png" });
|
|
220
|
+
await client.files.putString("notes.md", "# hello");
|
|
221
|
+
await client.files.putFromURL("cache/cat.jpg", "https://example.com/cat.jpg"); // the platform fetches it
|
|
222
|
+
|
|
223
|
+
// Read.
|
|
224
|
+
const blob = await client.files.get("assets/logo.png");
|
|
225
|
+
const meta = await client.files.stat("assets/logo.png"); // size, etag, last_modified, url
|
|
226
|
+
await client.files.exists("assets/logo.png");
|
|
227
|
+
|
|
228
|
+
// List, with an optional glob (*, **, ?, [abc]) matched against the whole path.
|
|
229
|
+
const page = await client.files.list({ prefix: "assets/", glob: "**/*.png", limit: 100 });
|
|
230
|
+
// page.next_cursor → pass as cursor for the next page
|
|
231
|
+
|
|
232
|
+
// Change.
|
|
233
|
+
await client.files.move("draft.md", "published.md");
|
|
234
|
+
await client.files.copy("template.md", "instances/today.md");
|
|
235
|
+
await client.files.del("old.log");
|
|
236
|
+
await client.files.deleteMany({ prefix: "tmp/" });
|
|
237
|
+
|
|
238
|
+
// Several operations in one round trip; each result has its own ok and error.
|
|
239
|
+
await client.files.batch([
|
|
240
|
+
{ op: "put_url", path: "a.jpg", src_url: "https://example.com/a.jpg" },
|
|
241
|
+
{ op: "copy", from: "a.jpg", to: "b.jpg" },
|
|
242
|
+
{ op: "del", path: "a.jpg" },
|
|
243
|
+
]);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Saving app state safely.** Every read returns the file's ETag. Writing with `ifMatch` fails with 412 if another tab or device changed the file since you read it, so a save never silently overwrites someone else's:
|
|
200
247
|
|
|
201
|
-
|
|
248
|
+
```ts
|
|
249
|
+
import { APIError } from "@runappsai/sdk";
|
|
250
|
+
|
|
251
|
+
const { value, etag } = await client.files.readJSON<{ score: number }>("state.json");
|
|
252
|
+
const next = { score: (value?.score ?? 0) + 1 };
|
|
253
|
+
try {
|
|
254
|
+
await client.files.put("state.json", JSON.stringify(next), {
|
|
255
|
+
contentType: "application/json",
|
|
256
|
+
...(etag ? { ifMatch: etag } : { ifNoneMatch: true }), // first save: must not exist yet
|
|
257
|
+
});
|
|
258
|
+
} catch (e) {
|
|
259
|
+
if (e instanceof APIError && e.statusCode === 412) { /* read again, merge, retry */ }
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`files.read(path)` is `get` plus the ETag, for files that aren't JSON. `ifNoneMatch: true` refuses to overwrite an existing file (409).
|
|
264
|
+
|
|
265
|
+
**Files from your own code.** A personal key has no app of its own, so name the app: `new RunApps({ apiKey, project: "my-tool" })`. You get your own files in that app, the same ones the app sees for you. It must be an app you have added or created.
|
|
266
|
+
|
|
267
|
+
Paths are at most 900 bytes, and a single file is at most 200 MB.
|
|
202
268
|
|
|
203
269
|
## Models
|
|
204
270
|
|
|
@@ -207,26 +273,26 @@ const all = await client.models.list();
|
|
|
207
273
|
const video = await client.models.list({ capability: "video_generation" });
|
|
208
274
|
```
|
|
209
275
|
|
|
210
|
-
|
|
276
|
+
`GET /v1/models` needs no key. Token prices are in pips per million tokens, where 1 USD is 1,000,000 pips. Each model carries `capability_tags`, such as `t2v`, `i2v`, `voice_clone` or `timestamps`; filter with `hasCapabilityTag`. Chat models list `input_modalities`; check with `acceptsModality`. Every model describes its accepted options through `getOptionsSchema(model)`: fields, bounds, enums, cross-field rules, and a catalogue of voices or emotions. `validateRequest(schema, body)` returns every problem at once for a form to show. Live prices are also on the [pricing page](https://www.runapps.ai/pricing).
|
|
211
277
|
|
|
212
278
|
## Image, audio, video, embeddings
|
|
213
279
|
|
|
214
280
|
```ts
|
|
215
|
-
// Image
|
|
281
|
+
// Image: a data: URI (sync) or a hosted URL (async); decodeMediaUrl() gives the bytes.
|
|
216
282
|
const img = await client.image.generate("MiniMax Image-01", {
|
|
217
283
|
prompt: "a developer at a laptop, anime style",
|
|
218
284
|
resolution: "2K", aspect_ratio: "16:9", // or size: "WxH"
|
|
219
285
|
reference_image_urls: ["https://…"],
|
|
220
286
|
});
|
|
221
287
|
await client.image.edit("GPT Image", { image: { data: bytes, filename: "photo.png" }, prompt: "add a party hat" });
|
|
222
|
-
// generateAsync
|
|
288
|
+
// generateAsync, or submitGenerate + getAsyncStatus, for jobs longer than about 100 s
|
|
223
289
|
|
|
224
290
|
// Speech.
|
|
225
291
|
const speech = await client.audio.speech("MiniMax Speech 2.6 HD", { input: "Hello", voice: "English_radiant_girl", emotion: "happy" });
|
|
226
292
|
const text = await client.audio.transcribe("OpenAI/Whisper", { file: { data: audioBytes, filename: "a.mp3" } });
|
|
227
293
|
// speechAsync / transcribeAsync for long jobs; voices and emotions are in getOptionsSchema(model).catalog
|
|
228
294
|
|
|
229
|
-
// Video
|
|
295
|
+
// Video: always async.
|
|
230
296
|
const task = await client.video.generate("MiniMax Hailuo 2.3", { prompt: "a gentle ocean wave", duration: 5 });
|
|
231
297
|
const status = await client.video.wait(task.id, { pollIntervalMs: 5000 });
|
|
232
298
|
if (status.status === "succeeded") await client.video.getContent(task.id);
|
|
@@ -235,9 +301,66 @@ if (status.status === "succeeded") await client.video.getContent(task.id);
|
|
|
235
301
|
const e = await client.embeddings.create("text-embedding-3-small", { input: ["alpha", "beta"] });
|
|
236
302
|
```
|
|
237
303
|
|
|
238
|
-
`VideoGenerateParams` covers keyframes (`first_frame_url
|
|
304
|
+
`VideoGenerateParams` covers keyframes (`first_frame_url`, `last_frame_url`), reference inputs, source video, image and audio drivers, `service_tier: "flex"`, and more. Every `*_url` accepts `https://` or a `data:` URI (`encodeImageUrl(bytes)`).
|
|
305
|
+
|
|
306
|
+
## HTTP API
|
|
307
|
+
|
|
308
|
+
Base URL `https://www.runapps.ai`. Send `Authorization: Bearer <key or app token>`.
|
|
309
|
+
|
|
310
|
+
| Request | Does |
|
|
311
|
+
|---|---|
|
|
312
|
+
| `POST /v1/chat/completions` | Chat, OpenAI format |
|
|
313
|
+
| `GET /v1/models` | Model catalogue and prices (no key needed) |
|
|
314
|
+
| `POST /v1/images/generations` | Generate images |
|
|
315
|
+
| `POST /v1/images/edits` | Edit an image |
|
|
316
|
+
| `POST /v1/audio/speech` | Text to speech |
|
|
317
|
+
| `POST /v1/audio/transcriptions` | Speech to text |
|
|
318
|
+
| `GET /v1/audio/voices` | Voices for a speech model |
|
|
319
|
+
| `POST /v1/videos/generations` | Start a video job |
|
|
320
|
+
| `GET /v1/videos/generations/<id>` | Job status |
|
|
321
|
+
| `GET /v1/videos/<id>/content` | The finished video |
|
|
322
|
+
| `POST /v1/embeddings` | Embeddings |
|
|
323
|
+
| `/v1/async/…` | Async versions of the image and audio calls: submit, then poll |
|
|
324
|
+
|
|
325
|
+
### Files over HTTP
|
|
326
|
+
|
|
327
|
+
With an app token the routes are under `/v1/files`. With a personal key, put the app in the path: `/v1/p/<slug>/files`.
|
|
328
|
+
|
|
329
|
+
| Request | Does |
|
|
330
|
+
|---|---|
|
|
331
|
+
| `GET /v1/files?prefix=&glob=&limit=&cursor=` | List files. `limit` defaults to 100, at most 1000. |
|
|
332
|
+
| `GET /v1/files/<path>` | Read a file. Returns `ETag`; sends 304 for a matching `If-None-Match`. |
|
|
333
|
+
| `HEAD /v1/files/<path>` | Metadata only. |
|
|
334
|
+
| `PUT /v1/files/<path>` | Write the request body. Honours `Content-Type`, `If-Match: <etag>` and `If-None-Match: *`. |
|
|
335
|
+
| `DELETE /v1/files/<path>` | Delete a file. |
|
|
336
|
+
| `POST /v1/files/move` | `{"from", "to"}` |
|
|
337
|
+
| `POST /v1/files/copy` | `{"from", "to"}` |
|
|
338
|
+
| `POST /v1/files/delete` | `{"prefix"}` or `{"glob"}` deletes many. |
|
|
339
|
+
| `POST /v1/files/put-url` | `{"path", "src_url"}`: the platform downloads the URL into the file. |
|
|
340
|
+
| `POST /v1/files/batch` | `{"ops": [...]}`: up to 64 operations, `put_url`, `del`, `move`, `copy`, `exists`, `stat`. |
|
|
341
|
+
|
|
342
|
+
A file object has `path`, `size`, `content_type`, `etag`, `last_modified` and `url`.
|
|
343
|
+
|
|
344
|
+
### Sign-in without the SDK
|
|
345
|
+
|
|
346
|
+
Send the user to:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
https://www.runapps.ai/api/sdk/grant?origin=<your origin>&redirect_to=<url to return to>&token_name=runapps_token[&project_id=<slug>]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The origin must be your app's own `https://<slug>.runapps.dev` or `http://localhost` on any port, and `redirect_to` must be on that origin. After signing in, the user comes back to `redirect_to` with `#runapps_token=<token>` in the URL fragment. Send it as `Authorization: Bearer <token>` on `/v1` calls. The token belongs to that user in that app. On localhost, pass your app's slug as `project_id`.
|
|
353
|
+
|
|
354
|
+
## Errors
|
|
239
355
|
|
|
240
|
-
|
|
356
|
+
| Status | Meaning |
|
|
357
|
+
|---|---|
|
|
358
|
+
| 400 | Bad request, such as an input the model doesn't accept. Nothing is charged. |
|
|
359
|
+
| 401 | Missing or invalid key or token. In browser sign-in mode the SDK refreshes the token and retries once. |
|
|
360
|
+
| 402 | The balance is too low, or today's budget is reached. |
|
|
361
|
+
| 409 | `If-None-Match: *` and the file already exists. |
|
|
362
|
+
| 412 | `If-Match` and the file changed since you read it. |
|
|
363
|
+
| 429 | Too many requests at once. Retry shortly. |
|
|
241
364
|
|
|
242
365
|
```ts
|
|
243
366
|
try {
|
|
@@ -248,9 +371,9 @@ try {
|
|
|
248
371
|
}
|
|
249
372
|
```
|
|
250
373
|
|
|
251
|
-
Every call takes `{ signal }
|
|
374
|
+
Every call takes `{ signal }` for cancellation.
|
|
252
375
|
|
|
253
|
-
##
|
|
376
|
+
## Reference
|
|
254
377
|
|
|
255
378
|
| Service | Methods |
|
|
256
379
|
|---|---|
|
|
@@ -262,13 +385,21 @@ Every call takes `{ signal }`. In browser-auth mode a 401 is retried once after
|
|
|
262
385
|
| `client.video` | `generate`, `getStatus`, `wait`, `getContent` |
|
|
263
386
|
| `client.embeddings` | `create` |
|
|
264
387
|
|
|
265
|
-
Browser
|
|
388
|
+
Browser sign-in: `client.signIn()`, `client.signOut()`, `client.user`, `client.auth`.
|
|
266
389
|
|
|
267
|
-
Helpers: `hasCapabilityTag`, `acceptsModality`, `getOptionsSchema`, `acceptsField`, `requiresField`, `allowedValuesFor`, `validateRequest`, `supportsVoiceClone`, `supportsInstructText`, `defaultVoice`, `encodeImageUrl`, `decodeMediaUrl`, the message builders
|
|
390
|
+
Helpers: `hasCapabilityTag`, `acceptsModality`, `getOptionsSchema`, `acceptsField`, `requiresField`, `allowedValuesFor`, `validateRequest`, `supportsVoiceClone`, `supportsInstructText`, `defaultVoice`, `encodeImageUrl`, `decodeMediaUrl`, `ServerTools`, and the message builders `userMessage`, `systemMessage`, `assistantMessage`, `toolResultMessage`, `userMessageParts`, `textPart`, `imagePart`, `videoPart`, `audioPart`.
|
|
391
|
+
|
|
392
|
+
### Addresses
|
|
393
|
+
|
|
394
|
+
| Address | What it serves |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `www.runapps.ai` | The website, dashboard, sign-in and the API (`/v1`) |
|
|
397
|
+
| `<slug>.runapps.dev` | Each app |
|
|
398
|
+
| `files.runapps.ai` | Public file links |
|
|
268
399
|
|
|
269
|
-
|
|
400
|
+
### Upgrading from `@runjobsai/sdk`
|
|
270
401
|
|
|
271
|
-
|
|
402
|
+
The package was renamed. Install `@runappsai/sdk` and change your imports; the API is the same. Users signed in with 0.2.1 or later stay signed in after the upgrade. `RunJobs` and `authProvider: "runjobs"` still work as deprecated aliases of `RunApps` and `authProvider: "runapps"`.
|
|
272
403
|
|
|
273
404
|
## License
|
|
274
405
|
|
package/dist/activity-tracker.js
CHANGED
|
@@ -82,7 +82,7 @@ export class ActivityTracker {
|
|
|
82
82
|
}
|
|
83
83
|
catch (err) {
|
|
84
84
|
// eslint-disable-next-line no-console
|
|
85
|
-
console.error("[
|
|
85
|
+
console.error("[runapps sdk] ActivityTracker onChange listener threw:", err);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
package/dist/browser-auth.d.ts
CHANGED
|
@@ -165,7 +165,8 @@ export declare class BrowserAuth {
|
|
|
165
165
|
private markSignedOut;
|
|
166
166
|
private clearSignedOut;
|
|
167
167
|
private removeBadge;
|
|
168
|
-
/** Parse the #
|
|
168
|
+
/** Parse the #runapps_token=… fragment (or the older #runjobs_token=…),
|
|
169
|
+
* install, then strip it. */
|
|
169
170
|
private consumeFragment;
|
|
170
171
|
/** postMessage handshake with the dashboard parent (iframe path). */
|
|
171
172
|
private requestTokenFromParent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtF,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtF,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA0DD;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C;2EACuE;IACvE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,GAAE,kBAAuB;IA+CzC,iEAAiE;IACjE,QAAQ,QAAa,OAAO,CAAC,MAAM,CAAC,CA0DlC;IAEF,sEAAsE;IACtE,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAQ3D,6CAA6C;IAC7C,IAAI,IAAI,IAAI,WAAW,GAAG,IAAI,CAE7B;IAED;;;;;;;;;;;OAWG;IACH,aAAa,IAAI,OAAO;IAIxB;;;;;;;;;OASG;IACH,OAAO,IAAI,IAAI;IAWf;;;;;;OAMG;IACH,UAAU,IAAI,IAAI;IASlB,0CAA0C;IAC1C,MAAM,IAAI,IAAI;IAmBd;;;;;;;OAOG;IACH,qBAAqB,CAAC,IAAI,EAAE;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;KAC1B,GAAG,MAAM;IAIV;;;;;;OAMG;IACH,mBAAmB,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAO1D,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,aAAa;IA+BrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,WAAW;IAKnB;kCAC8B;IAC9B,OAAO,CAAC,eAAe;IAmCvB,qEAAqE;IACrE,OAAO,CAAC,sBAAsB;IAqD9B;;;;uEAImE;IACnE,OAAO,CAAC,WAAW;CAqCpB"}
|