@runappsai/sdk 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runappsai/sdk",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "JavaScript / TypeScript client for the RunApps AI Gateway",
5
5
  "license": "MIT",
6
6
  "author": "RunApps",
package/src/files.ts CHANGED
@@ -48,6 +48,14 @@ export interface FileRead {
48
48
  etag?: string;
49
49
  }
50
50
 
51
+ export interface SignOptions {
52
+ /** How long the link stays valid, in seconds. Default 3600, at most 7 days. */
53
+ expiresIn?: number;
54
+ /** When true the link downloads instead of displaying inline. */
55
+ download?: boolean;
56
+ signal?: AbortSignal;
57
+ }
58
+
51
59
  export interface ListOptions {
52
60
  prefix?: string;
53
61
  cursor?: string;
@@ -343,6 +351,37 @@ export class FilesService {
343
351
  return this.run("files.copy", async () => this.transport.postJSON<FileObject>((await this.base()) + "/copy", { from, to }, init));
344
352
  }
345
353
 
354
+ /**
355
+ * A time-limited link to a private file that needs no token: put it in
356
+ * `<img src>`, `<video src>` or `<a href download>`. The link is for
357
+ * this user's copy of the file in this app; it expires after
358
+ * `expiresIn` seconds (default one hour, at most seven days) and is
359
+ * never cached, so it always shows the current bytes. For a permanent
360
+ * public address use the FileObject's `url` instead.
361
+ */
362
+ async url(path: string, opts?: SignOptions): Promise<string> {
363
+ const urls = await this.urls([path], opts);
364
+ const u = urls[path];
365
+ if (!u) throw new Error("RunApps: no signed URL returned for " + path);
366
+ return u;
367
+ }
368
+
369
+ /** Signed links for several paths at once (up to 200): `{ path: url }`. */
370
+ async urls(paths: string[], opts?: SignOptions): Promise<Record<string, string>> {
371
+ const body: { paths: string[]; expires_in?: number } = { paths };
372
+ if (opts?.expiresIn) body.expires_in = Math.floor(opts.expiresIn);
373
+ const r = await this.run("files.sign", async () => this.transport.postJSON<{ urls: Record<string, string> }>(
374
+ (await this.base()) + "/sign",
375
+ body,
376
+ opts?.signal ? { signal: opts.signal } : undefined,
377
+ ));
378
+ const out: Record<string, string> = {};
379
+ for (const [p, u] of Object.entries(r.urls || {})) {
380
+ out[p] = opts?.download ? u + (u.includes("?") ? "&" : "?") + "download=1" : u;
381
+ }
382
+ return out;
383
+ }
384
+
346
385
  /**
347
386
  * Ask the gateway to fetch `srcURL` and store the body at `path`.
348
387
  * Saves a round trip vs downloading then re-uploading. `srcURL`
package/src/index.ts CHANGED
@@ -88,6 +88,7 @@ export {
88
88
  supportsVoiceClone,
89
89
  supportsInstructText,
90
90
  defaultVoice,
91
+ defaultModel,
91
92
  } from "./models.js";
92
93
 
93
94
  // Model options schema (input validation + capability introspection)
@@ -168,6 +169,7 @@ export type {
168
169
  FileObject,
169
170
  FileListResult,
170
171
  PutOptions as FilePutOptions,
172
+ SignOptions as FileSignOptions,
171
173
  ListOptions as FileListOptions,
172
174
  PutFromURLOptions as FilePutFromURLOptions,
173
175
  BatchOp as FileBatchOp,
package/src/models.ts CHANGED
@@ -10,6 +10,15 @@ export interface Model {
10
10
  object: string;
11
11
  capability: string;
12
12
  provider?: string;
13
+ /**
14
+ * The model the platform uses for this capability when a request names
15
+ * none: the one the admin starred in the gateway settings, else the
16
+ * first of the capability. Exactly one model per capability carries it.
17
+ * Use `defaultModel` to pick it out of a list.
18
+ */
19
+ is_default?: boolean;
20
+ /** One or two sentences about the model, set on the gateway. */
21
+ description?: string;
13
22
  /**
14
23
  * Server-derived "what this model can actually do" tags
15
24
  * (e.g. {id:"t2v", label:"Text-to-Video"}, {id:"i2v", label:"Image-to-Video"}).
@@ -100,6 +109,18 @@ export const defaultVoice = (model: Model): string | null => {
100
109
  return typeof v === "string" ? v : null;
101
110
  };
102
111
 
112
+ /**
113
+ * The platform's default model among `models` — the admin-starred one for
114
+ * the capability, else the first of the capability. With `capability`
115
+ * given, only that capability is considered; without it, the first
116
+ * default in list order (so pass a list from
117
+ * `client.models.list({ capability })`). Null when the list has none.
118
+ */
119
+ export const defaultModel = (models: Model[], capability?: string): Model | null => {
120
+ const pool = capability ? models.filter((m) => m.capability === capability) : models;
121
+ return pool.find((m) => m.is_default) ?? null;
122
+ };
123
+
103
124
  export class ModelsService {
104
125
  constructor(private readonly transport: Transport) {}
105
126
 
@@ -120,4 +141,14 @@ export class ModelsService {
120
141
  const resp = await this.transport.getPublicJSON<{ data: Model[] }>(path, init);
121
142
  return resp.data;
122
143
  }
144
+
145
+ /**
146
+ * The platform's default model for a capability (`"text"` unless given):
147
+ * the admin-starred one, else the first. What a request without `model`
148
+ * would use before the user's own preference. Null when the capability
149
+ * has no models.
150
+ */
151
+ async default(capability = "text", init?: { signal?: AbortSignal }): Promise<Model | null> {
152
+ return defaultModel(await this.list({ capability }, init), capability);
153
+ }
123
154
  }