@runappsai/sdk 0.3.1 → 0.5.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.
@@ -12,8 +12,8 @@ export interface TransportOptions {
12
12
  apiKey?: string;
13
13
  /** Dynamic resolver — called immediately before each request. Use this
14
14
  * when the token is short-lived and refreshed externally (e.g. running
15
- * inside a runjobs resource bundle that exposes
16
- * `window.runjobs.getToken()`). The result is awaited per request, so
15
+ * inside an app bundle that exposes
16
+ * its own `getToken()`). The result is awaited per request, so
17
17
  * the caller can return a Promise that hits a token endpoint. Wins
18
18
  * over `apiKey` when both are supplied. */
19
19
  apiKeyResolver?: () => string | Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runappsai/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "JavaScript / TypeScript client for the RunApps AI Gateway",
5
5
  "license": "MIT",
6
6
  "author": "RunApps",
@@ -153,7 +153,7 @@ export class ActivityTracker {
153
153
  h();
154
154
  } catch (err) {
155
155
  // eslint-disable-next-line no-console
156
- console.error("[runjobs sdk] ActivityTracker onChange listener threw:", err);
156
+ console.error("[runapps sdk] ActivityTracker onChange listener threw:", err);
157
157
  }
158
158
  }
159
159
  }
@@ -1,19 +1,19 @@
1
1
  // browser-auth.ts — opt-in runapps.ai auth flow for browser bundles.
2
2
  //
3
- // Activated by `new RunApps({ authProvider: "runjobs", baseURL: "..." })`.
3
+ // Activated by `new RunApps({ authProvider: "runapps", baseURL: "..." })`.
4
4
  // In Node and other non-window environments this module's runtime code
5
5
  // short-circuits to no-ops; it ships with the IIFE bundle for resource
6
6
  // bundles loaded via <script> tag.
7
7
  //
8
8
  // Responsibilities:
9
- // - Detect & consume `#runjobs_token=…` URL fragment planted by the
9
+ // - Detect & consume the `#runapps_token=…` URL fragment planted by the
10
10
  // grant page after a successful sign-in.
11
11
  // - Trigger a redirect-based grant flow when the bundle calls a
12
12
  // gateway endpoint without a fresh token.
13
- // - In iframe contexts (the runjobs dashboard embedding), handshake
13
+ // - In iframe contexts (a RunApps page embedding the app), handshake
14
14
  // with the parent via postMessage to silently obtain a token.
15
15
  // - Render an unobtrusive identity badge in the bottom-right corner
16
- // so the end user can see WHICH runjobs account is paying for the
16
+ // so the end user can see WHICH RunApps account is paying for the
17
17
  // calls being made on their behalf.
18
18
  //
19
19
  // Why redirect (not popup): under COOP=same-origin (required so the
@@ -103,7 +103,10 @@ const TOKEN_REFRESH_MARGIN_S = 60;
103
103
  // project (iframe / parent-handshake mode) fall back to the legacy
104
104
  // unsuffixed key, which preserves back-compat for already-persisted
105
105
  // tokens after upgrade.
106
- const STORAGE_KEY_BASE = "__runjobs_auth_v1__";
106
+ const STORAGE_KEY_BASE = "__runapps_auth_v1__";
107
+ // Written by SDK 0.3 and earlier; read once and migrated, so an upgrade
108
+ // keeps the user signed in.
109
+ const LEGACY_STORAGE_KEY_BASE = "__runjobs_auth_v1__";
107
110
  // Set when the user clicks "Sign out". While present, getToken() must
108
111
  // not silently redirect to the grant page even though the user still
109
112
  // has a runapps.ai cookie — otherwise the backend's auto-grant turns
@@ -111,7 +114,8 @@ const STORAGE_KEY_BASE = "__runjobs_auth_v1__";
111
114
  // signIn() so the user can come back. Same per-project namespacing
112
115
  // rationale as STORAGE_KEY_BASE — signing out of bundle A shouldn't
113
116
  // also sign out bundle B that happens to share the origin.
114
- const SIGNED_OUT_KEY_BASE = "__runjobs_signed_out_v1__";
117
+ const SIGNED_OUT_KEY_BASE = "__runapps_signed_out_v1__";
118
+ const LEGACY_SIGNED_OUT_KEY_BASE = "__runjobs_signed_out_v1__";
115
119
 
116
120
  /** Compose the localStorage key for an auth slot. `null` project
117
121
  * yields the base key (iframe-mode and pre-v1.x bundles), which
@@ -125,6 +129,14 @@ function signedOutKey(project: string | null): string {
125
129
  return project ? `${SIGNED_OUT_KEY_BASE}:${project}` : SIGNED_OUT_KEY_BASE;
126
130
  }
127
131
 
132
+ function legacyStorageKey(project: string | null): string {
133
+ return project ? `${LEGACY_STORAGE_KEY_BASE}:${project}` : LEGACY_STORAGE_KEY_BASE;
134
+ }
135
+
136
+ function legacySignedOutKey(project: string | null): string {
137
+ return project ? `${LEGACY_SIGNED_OUT_KEY_BASE}:${project}` : LEGACY_SIGNED_OUT_KEY_BASE;
138
+ }
139
+
128
140
  interface PersistedAuth {
129
141
  token: string;
130
142
  expiresAt: number;
@@ -398,6 +410,9 @@ export class BrowserAuth {
398
410
  params.set("redirect_to", args.redirectTo);
399
411
  params.set("scheme", args.scheme);
400
412
  if (this.project) params.set("project_id", this.project);
413
+ // Ask for #runapps_token=…; a platform that predates it answers with
414
+ // #runjobs_token=…, which consumeFragment also accepts.
415
+ params.set("token_name", "runapps_token");
401
416
  return this.origin + "/api/sdk/grant?" + params.toString();
402
417
  }
403
418
 
@@ -454,8 +469,14 @@ export class BrowserAuth {
454
469
  private loadPersisted() {
455
470
  if (typeof localStorage === "undefined") return;
456
471
  try {
457
- const raw = localStorage.getItem(storageKey(this.project));
458
- if (!raw) return;
472
+ let raw = localStorage.getItem(storageKey(this.project));
473
+ if (!raw) {
474
+ // Upgrade from SDK 0.3 or earlier: move the token to the new key.
475
+ raw = localStorage.getItem(legacyStorageKey(this.project));
476
+ if (!raw) return;
477
+ localStorage.setItem(storageKey(this.project), raw);
478
+ localStorage.removeItem(legacyStorageKey(this.project));
479
+ }
459
480
  const data = JSON.parse(raw) as Partial<PersistedAuth>;
460
481
  if (!data.token || !data.origin) return;
461
482
  // Pin to the gateway origin we were constructed with — a bundle
@@ -480,13 +501,15 @@ export class BrowserAuth {
480
501
  if (typeof localStorage === "undefined") return;
481
502
  try {
482
503
  localStorage.removeItem(storageKey(this.project));
504
+ localStorage.removeItem(legacyStorageKey(this.project));
483
505
  } catch { /* ignore */ }
484
506
  }
485
507
 
486
508
  private isSignedOut(): boolean {
487
509
  if (typeof localStorage === "undefined") return false;
488
510
  try {
489
- return localStorage.getItem(signedOutKey(this.project)) === "1";
511
+ return localStorage.getItem(signedOutKey(this.project)) === "1" ||
512
+ localStorage.getItem(legacySignedOutKey(this.project)) === "1";
490
513
  } catch { return false; }
491
514
  }
492
515
 
@@ -501,17 +524,19 @@ export class BrowserAuth {
501
524
  if (typeof localStorage === "undefined") return;
502
525
  try {
503
526
  localStorage.removeItem(signedOutKey(this.project));
527
+ localStorage.removeItem(legacySignedOutKey(this.project));
504
528
  } catch { /* ignore */ }
505
529
  }
506
530
 
507
531
  private removeBadge() {
508
532
  if (typeof document === "undefined") return;
509
- document.getElementById("__runjobs_identity__")?.remove();
533
+ document.getElementById("__runapps_identity__")?.remove();
510
534
  }
511
535
 
512
- /** Parse the #runjobs_token=… fragment, install, then strip it. */
536
+ /** Parse the #runapps_token=… fragment (or the older #runjobs_token=…),
537
+ * install, then strip it. */
513
538
  private consumeFragment(): boolean {
514
- if (!location.hash || location.hash.indexOf("runjobs_token=") < 0)
539
+ if (!location.hash || !/(^#|&)run(apps|jobs)_token=/.test(location.hash))
515
540
  return false;
516
541
  const params: Record<string, string> = {};
517
542
  location.hash
@@ -521,7 +546,8 @@ export class BrowserAuth {
521
546
  const i = kv.indexOf("=");
522
547
  if (i >= 0) params[decodeURIComponent(kv.slice(0, i))] = decodeURIComponent(kv.slice(i + 1));
523
548
  });
524
- if (!params.runjobs_token) return false;
549
+ const token = params.runapps_token || params.runjobs_token;
550
+ if (!token) return false;
525
551
  let user: BrowserUser | undefined;
526
552
  if (params.user) {
527
553
  try {
@@ -531,7 +557,7 @@ export class BrowserAuth {
531
557
  }
532
558
  }
533
559
  this.setToken(
534
- params.runjobs_token,
560
+ token,
535
561
  parseInt(params.expires_at ?? "", 10) || 0,
536
562
  user,
537
563
  );
@@ -559,7 +585,7 @@ export class BrowserAuth {
559
585
  const data = e.data as
560
586
  | { type: string; token?: string; expires_at?: number; user?: BrowserUser }
561
587
  | null;
562
- if (data && data.type === "runjobs:token" && data.token) {
588
+ if (data && (data.type === "runapps:token" || data.type === "runjobs:token") && data.token) {
563
589
  this.setToken(data.token, data.expires_at ?? 0, data.user);
564
590
  cleanup();
565
591
  resolve();
@@ -576,7 +602,8 @@ export class BrowserAuth {
576
602
  const post = () => {
577
603
  if (done) return;
578
604
  try {
579
- window.parent.postMessage({ type: "runjobs:request-token" }, this.origin);
605
+ window.parent.postMessage({ type: "runapps:request-token" }, this.origin);
606
+ window.parent.postMessage({ type: "runjobs:request-token" }, this.origin); // older embedders
580
607
  } catch (err) {
581
608
  cleanup();
582
609
  reject(err);
@@ -604,7 +631,7 @@ export class BrowserAuth {
604
631
  * can't reach back into the bundle window via `window.opener`. */
605
632
  private renderBadge() {
606
633
  if (typeof document === "undefined" || !this.userInfo) return;
607
- const ID = "__runjobs_identity__";
634
+ const ID = "__runapps_identity__";
608
635
  const dashboardUrl = `${this.origin}/dashboard`;
609
636
  const ready = (cb: () => void) =>
610
637
  document.body ? cb() : document.addEventListener("DOMContentLoaded", cb);
@@ -689,7 +716,7 @@ interface MountOpts {
689
716
  * downward); `right` vs `left` flips horizontal anchor on both.
690
717
  */
691
718
  /**
692
- * Inject the `@keyframes __runjobs_led_pulse__` rule into the document
719
+ * Inject the `@keyframes __runapps_led_pulse__` rule into the document
693
720
  * head ONCE (idempotent — re-mounts and multiple SDK instances on the
694
721
  * same page share the same rule). Must run at badge mount time so
695
722
  * the LED's `animation` CSS resolves the keyframe reference the
@@ -699,11 +726,11 @@ interface MountOpts {
699
726
  */
700
727
  function ensurePulseKeyframes(): void {
701
728
  if (typeof document === "undefined") return;
702
- if (document.getElementById("__runjobs_kf__")) return;
729
+ if (document.getElementById("__runapps_kf__")) return;
703
730
  const style = document.createElement("style");
704
- style.id = "__runjobs_kf__";
731
+ style.id = "__runapps_kf__";
705
732
  style.textContent =
706
- "@keyframes __runjobs_led_pulse__ {" +
733
+ "@keyframes __runapps_led_pulse__ {" +
707
734
  " 0% { box-shadow: 0 0 0 0 rgba(59,130,246,0.7); }" +
708
735
  " 70% { box-shadow: 0 0 0 6px rgba(59,130,246,0); }" +
709
736
  " 100% { box-shadow: 0 0 0 0 rgba(59,130,246,0); }" +
@@ -740,7 +767,8 @@ function badgeCornerStyles(position: BadgePosition): {
740
767
 
741
768
  /** Cookie key the saved badge position lives under. URI-safe, prefixed
742
769
  * so other libraries / consumers don't accidentally collide. */
743
- const BADGE_POS_COOKIE = "__runjobs_badge_pos__";
770
+ const BADGE_POS_COOKIE = "__runapps_badge_pos__";
771
+ const LEGACY_BADGE_POS_COOKIE = "__runjobs_badge_pos__";
744
772
 
745
773
  /** Pixel threshold a pointer must travel before we treat the gesture
746
774
  * as a drag instead of a click. Below this, it's a normal badge tap
@@ -754,12 +782,15 @@ const BADGE_EDGE_INSET_PX = 8;
754
782
 
755
783
  function readBadgePosCookie(): { x: number; y: number } | null {
756
784
  if (typeof document === "undefined") return null;
757
- const raw = document.cookie
758
- .split(";")
759
- .map((s) => s.trim())
760
- .find((s) => s.startsWith(BADGE_POS_COOKIE + "="));
785
+ const cookies = document.cookie.split(";").map((s) => s.trim());
786
+ let name = BADGE_POS_COOKIE;
787
+ let raw = cookies.find((s) => s.startsWith(name + "="));
788
+ if (!raw) {
789
+ name = LEGACY_BADGE_POS_COOKIE;
790
+ raw = cookies.find((s) => s.startsWith(name + "="));
791
+ }
761
792
  if (!raw) return null;
762
- const value = decodeURIComponent(raw.slice(BADGE_POS_COOKIE.length + 1));
793
+ const value = decodeURIComponent(raw.slice(name.length + 1));
763
794
  const parts = value.split(",");
764
795
  const rawX = parts[0];
765
796
  const rawY = parts[1];
@@ -1322,7 +1353,7 @@ function updateLED(led: HTMLSpanElement, snap: ActivitySnapshot): void {
1322
1353
  // CSS-driven pulse via box-shadow ring expanding outward —
1323
1354
  // single keyframe loop, cheaper than animating opacity.
1324
1355
  led.style.boxShadow = "0 0 0 0 rgba(59,130,246,0.7)";
1325
- led.style.animation = "__runjobs_led_pulse__ 1.2s ease-out infinite";
1356
+ led.style.animation = "__runapps_led_pulse__ 1.2s ease-out infinite";
1326
1357
  break;
1327
1358
  case "error":
1328
1359
  led.style.background = "#ef4444";
@@ -1353,14 +1384,14 @@ function createPopover(dashboardUrl: string, anchorCss: string, onSignOut: () =>
1353
1384
  "cursor:default",
1354
1385
  "text-align:left",
1355
1386
  ].join(";");
1356
- pop.setAttribute("data-runjobs-popover", "1");
1387
+ pop.setAttribute("data-runapps-popover", "1");
1357
1388
  pop.addEventListener("click", (e) => e.stopPropagation());
1358
1389
 
1359
1390
  // The popover's content is rebuilt on every redraw to keep DOM
1360
1391
  // diffing simple. ~3 sections × ~6 children = ~20 nodes, no
1361
1392
  // measurable cost at the 6 Hz redraw rate.
1362
1393
  const inner = document.createElement("div");
1363
- inner.setAttribute("data-runjobs-popover-content", "1");
1394
+ inner.setAttribute("data-runapps-popover-content", "1");
1364
1395
  pop.appendChild(inner);
1365
1396
 
1366
1397
  const footer = document.createElement("div");
@@ -1411,7 +1442,7 @@ function createPopover(dashboardUrl: string, anchorCss: string, onSignOut: () =>
1411
1442
  }
1412
1443
 
1413
1444
  function updatePopover(pop: HTMLDivElement, snap: ActivitySnapshot): void {
1414
- const inner = pop.querySelector('[data-runjobs-popover-content]') as HTMLDivElement | null;
1445
+ const inner = pop.querySelector('[data-runapps-popover-content]') as HTMLDivElement | null;
1415
1446
  if (!inner) return;
1416
1447
  inner.innerHTML = "";
1417
1448
  const now = Date.now();
package/src/client.ts CHANGED
@@ -27,13 +27,13 @@ const DEFAULT_BASE_URL = "https://www.runapps.ai";
27
27
  * in browsers — in Node this falls back to throwing the same
28
28
  * "missing apiKey" error you'd get with no auth config.
29
29
  */
30
- // "runapps" is the current name of the platform sign-in; "runjobs" is the
31
- // same thing under the name from before the rename.
30
+ // "runapps" turns on the platform sign-in. "runjobs" is a deprecated
31
+ // alias for it, kept so code written before the rename keeps working.
32
32
  export type AuthProvider = "static" | "runapps" | "runjobs";
33
33
 
34
34
  export interface ClientOptions {
35
- /** Static gateway API key (typically prefixed `gw-` / `rj_` / `rrt_`).
36
- * Required unless `apiKeyResolver` or `authProvider: "runapps"` is supplied. */
35
+ /** A personal API key (`rk_…`) or another static key. Leave it out in an
36
+ * app published on RunApps: the browser client then signs the user in. */
37
37
  apiKey?: string;
38
38
  /**
39
39
  * Dynamic API key resolver. See `AuthProvider` for the typical
@@ -43,13 +43,13 @@ export interface ClientOptions {
43
43
  */
44
44
  apiKeyResolver?: () => string | Promise<string>;
45
45
  /**
46
- * Auth strategy — defaults to `"static"`. Pass `"runapps"` to
47
- * activate the built-in browser auth flow against runapps.ai;
48
- * baseURL defaults to `https://www.runapps.ai` in that mode.
46
+ * Auth strategy. Rarely needed: in a browser with no `apiKey` /
47
+ * `apiKeyResolver` the client uses `"runapps"` (the platform sign-in),
48
+ * otherwise `"static"` (the key you pass).
49
49
  */
50
50
  authProvider?: AuthProvider;
51
51
  /**
52
- * Show the floating identity badge in `runjobs` auth mode.
52
+ * Show the floating identity badge in `runapps` auth mode.
53
53
  * **Default `true`** — the badge is now a desktop-ball-style
54
54
  * real-time activity indicator (LED dot + progress ring around
55
55
  * the avatar + click-through popover with active calls / recent
@@ -104,7 +104,7 @@ export interface ClientOptions {
104
104
  * ```html
105
105
  * <script src="https://cdn.jsdelivr.net/npm/@runappsai/sdk/dist/sdk.umd.js"></script>
106
106
  * <script>
107
- * const client = new RunApps.Client({ authProvider: "runapps" });
107
+ * const client = new RunApps();
108
108
  * const res = await client.chat.create({
109
109
  * model: "gpt-4o-mini",
110
110
  * messages: [{ role: "user", content: "hello" }],
@@ -116,7 +116,7 @@ export interface ClientOptions {
116
116
  *
117
117
  * ```ts
118
118
  * import { RunApps } from "@runappsai/sdk";
119
- * const client = new RunApps({ apiKey: process.env.RUNJOBS_API_KEY! });
119
+ * const client = new RunApps({ apiKey: process.env.RUNAPPS_API_KEY! });
120
120
  * ```
121
121
  */
122
122
  export class RunApps {
@@ -152,7 +152,14 @@ export class RunApps {
152
152
  readonly events: SDKEvents = new SDKEvents();
153
153
 
154
154
  constructor(options: ClientOptions = {}) {
155
- const provider = options.authProvider ?? "static";
155
+ // With no key and no explicit authProvider, a browser client uses the
156
+ // platform sign-in: `new RunApps()` is all an app needs. Elsewhere
157
+ // (Node, workers without a window) there is no one to sign in.
158
+ const provider =
159
+ options.authProvider ??
160
+ (!options.apiKey && !options.apiKeyResolver && typeof window !== "undefined"
161
+ ? "runapps"
162
+ : "static");
156
163
  let apiKeyResolver = options.apiKeyResolver;
157
164
  let baseURL = options.baseURL;
158
165
  let onUnauthorized: (() => void) | undefined;
@@ -197,7 +204,7 @@ export class RunApps {
197
204
 
198
205
  if (!options.apiKey && !apiKeyResolver) {
199
206
  throw new Error(
200
- "runapps: pass either `apiKey`, `apiKeyResolver`, or `authProvider: \"runapps\"`",
207
+ "runapps: pass `apiKey` (or `apiKeyResolver`); outside a browser there is no sign-in to fall back to",
201
208
  );
202
209
  }
203
210
  const transport = new Transport({
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@
6
6
  * reaching into per-service modules.
7
7
  */
8
8
 
9
+ // `RunJobs` is a deprecated alias of `RunApps`, kept for code written
10
+ // before the rename.
9
11
  export { RunApps, RunApps as Client, RunApps as RunJobs, type ClientOptions, type AuthProvider } from "./client.js";
10
12
 
11
13
  // SDK telemetry — `client.events` fires these around every LLM-ish
package/src/media.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * @example
22
22
  * ```ts
23
- * import { encodeImageUrl } from "@runjobs/sdk";
23
+ * import { encodeImageUrl } from "@runappsai/sdk";
24
24
  * const png = await fs.readFile("frame.png");
25
25
  * await client.video.generate("Veo 3.1", {
26
26
  * prompt: "...",
@@ -75,7 +75,7 @@ export function encodeImageUrl(
75
75
  *
76
76
  * @example
77
77
  * ```ts
78
- * import { decodeMediaUrl } from "@runjobs/sdk";
78
+ * import { decodeMediaUrl } from "@runappsai/sdk";
79
79
  * const job = await client.image.generateAsync("Seedream 5.0", {...});
80
80
  * const { bytes, contentType } = await decodeMediaUrl(job.data[0].url);
81
81
  * await fs.writeFile("out.png", bytes);
package/src/transport.ts CHANGED
@@ -14,8 +14,8 @@ export interface TransportOptions {
14
14
  apiKey?: string;
15
15
  /** Dynamic resolver — called immediately before each request. Use this
16
16
  * when the token is short-lived and refreshed externally (e.g. running
17
- * inside a runjobs resource bundle that exposes
18
- * `window.runjobs.getToken()`). The result is awaited per request, so
17
+ * inside an app bundle that exposes
18
+ * its own `getToken()`). The result is awaited per request, so
19
19
  * the caller can return a Promise that hits a token endpoint. Wins
20
20
  * over `apiKey` when both are supplied. */
21
21
  apiKeyResolver?: () => string | Promise<string>;