@sable-ai/sdk-core 0.1.3 → 0.1.4
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 +71 -21
- package/dist/esm/index.js +1 -1
- package/dist/sable-core.mjs +1486 -0
- package/dist/sable.iife.js +1 -1486
- package/dist/types/loader.d.ts +23 -0
- package/dist/types/session/index.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +5 -3
- package/src/index.test.ts +2 -2
- package/src/loader.ts +143 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sable-ai/sdk-core — loader stub (IIFE entry for the CDN bundle).
|
|
3
|
+
*
|
|
4
|
+
* This file compiles to `dist/sable.iife.js` (~1 KB gzipped). It is the
|
|
5
|
+
* ONLY script a customer loads on page render; the full SDK (~150 KB
|
|
6
|
+
* gzipped including livekit-client) is lazy-loaded as `sable-core.mjs` on
|
|
7
|
+
* the first call to `Sable.start()`. Pages that never open a session pay
|
|
8
|
+
* just the loader cost.
|
|
9
|
+
*
|
|
10
|
+
* Architecture (Stripe.js pattern):
|
|
11
|
+
*
|
|
12
|
+
* Page load: <script src="sdk.withsable.com/v1/sable.js"> → loader (1 KB)
|
|
13
|
+
* User click: Sable.start() → dynamic import of sable-core.mjs
|
|
14
|
+
* Steady state: 1 KB for pages that never start a session
|
|
15
|
+
*
|
|
16
|
+
* The loader pins itself to the same path as sable-core.mjs: both are
|
|
17
|
+
* served from `/v<version>/`, and the loader derives the core URL from
|
|
18
|
+
* its own script src (`document.currentScript.src`). A customer loading
|
|
19
|
+
* `/v0.1.4/sable.js` always pulls `/v0.1.4/sable-core.mjs`; a customer
|
|
20
|
+
* loading `/v1/sable.js` gets `/v1/sable-core.mjs`. Version cohesion is
|
|
21
|
+
* automatic — customers never touch the core URL.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
@@ -23,7 +23,7 @@ import type { SableAPI, SableEventHandler, SableEvents, StartOptions } from "../
|
|
|
23
23
|
* state ownership explicit and the teardown path easier to reason about.
|
|
24
24
|
*/
|
|
25
25
|
export declare class Session implements SableAPI {
|
|
26
|
-
readonly version = "0.1.
|
|
26
|
+
readonly version = "0.1.4";
|
|
27
27
|
private readonly emitter;
|
|
28
28
|
private activeRoom;
|
|
29
29
|
private visionHandle;
|
package/dist/types/version.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sable-ai/sdk-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Sable SDK core — headless runtime (voice + vision + agent RPC) that runs in the user's browser.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
|
8
8
|
"./dist/sable.iife.js",
|
|
9
|
+
"./dist/sable-core.mjs",
|
|
9
10
|
"./dist/esm/index.js"
|
|
10
11
|
],
|
|
11
12
|
"main": "./dist/esm/index.js",
|
|
@@ -33,8 +34,9 @@
|
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"clean": "rm -rf dist",
|
|
36
|
-
"build": "bun run clean && bun run build:
|
|
37
|
-
"build:
|
|
37
|
+
"build": "bun run clean && bun run build:loader && bun run build:core && bun run build:esm && bun run build:types",
|
|
38
|
+
"build:loader": "bun build src/loader.ts --outfile dist/sable.iife.js --format=iife --minify --target=browser",
|
|
39
|
+
"build:core": "bun build src/index.ts --outfile dist/sable-core.mjs --format=esm --minify --target=browser",
|
|
38
40
|
"build:esm": "bun build src/index.ts --outdir dist/esm --format=esm --target=browser --external livekit-client",
|
|
39
41
|
"build:types": "tsc -p tsconfig.build.json",
|
|
40
42
|
"typecheck": "tsc --noEmit",
|
package/src/index.test.ts
CHANGED
package/src/loader.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sable-ai/sdk-core — loader stub (IIFE entry for the CDN bundle).
|
|
3
|
+
*
|
|
4
|
+
* This file compiles to `dist/sable.iife.js` (~1 KB gzipped). It is the
|
|
5
|
+
* ONLY script a customer loads on page render; the full SDK (~150 KB
|
|
6
|
+
* gzipped including livekit-client) is lazy-loaded as `sable-core.mjs` on
|
|
7
|
+
* the first call to `Sable.start()`. Pages that never open a session pay
|
|
8
|
+
* just the loader cost.
|
|
9
|
+
*
|
|
10
|
+
* Architecture (Stripe.js pattern):
|
|
11
|
+
*
|
|
12
|
+
* Page load: <script src="sdk.withsable.com/v1/sable.js"> → loader (1 KB)
|
|
13
|
+
* User click: Sable.start() → dynamic import of sable-core.mjs
|
|
14
|
+
* Steady state: 1 KB for pages that never start a session
|
|
15
|
+
*
|
|
16
|
+
* The loader pins itself to the same path as sable-core.mjs: both are
|
|
17
|
+
* served from `/v<version>/`, and the loader derives the core URL from
|
|
18
|
+
* its own script src (`document.currentScript.src`). A customer loading
|
|
19
|
+
* `/v0.1.4/sable.js` always pulls `/v0.1.4/sable-core.mjs`; a customer
|
|
20
|
+
* loading `/v1/sable.js` gets `/v1/sable-core.mjs`. Version cohesion is
|
|
21
|
+
* automatic — customers never touch the core URL.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { VERSION } from "./version";
|
|
25
|
+
import type {
|
|
26
|
+
SableAPI,
|
|
27
|
+
SableEvents,
|
|
28
|
+
SableEventHandler,
|
|
29
|
+
StartOptions,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
// ── Script URL capture ────────────────────────────────────────────────────
|
|
33
|
+
//
|
|
34
|
+
// `document.currentScript` is only defined during SYNCHRONOUS script
|
|
35
|
+
// evaluation. We MUST read it at IIFE top-level, before any async boundary,
|
|
36
|
+
// or it will be null by the time start() is called.
|
|
37
|
+
|
|
38
|
+
const scriptSrc: string =
|
|
39
|
+
typeof document !== "undefined" &&
|
|
40
|
+
document.currentScript instanceof HTMLScriptElement
|
|
41
|
+
? document.currentScript.src
|
|
42
|
+
: "";
|
|
43
|
+
|
|
44
|
+
// ── Pre-load subscription buffer ──────────────────────────────────────────
|
|
45
|
+
//
|
|
46
|
+
// Customers may call `Sable.on(...)` before `Sable.start(...)` — e.g. to
|
|
47
|
+
// wire up UI observers on page load. We record those subscriptions here
|
|
48
|
+
// and forward them to the real session once the core loads. Each entry
|
|
49
|
+
// tracks a `cancelled` flag + the real unsub function so the loader's
|
|
50
|
+
// returned unsub can reach through to the real subscription after load.
|
|
51
|
+
|
|
52
|
+
interface PendingSub {
|
|
53
|
+
event: keyof SableEvents;
|
|
54
|
+
handler: SableEventHandler<keyof SableEvents>;
|
|
55
|
+
realUnsub?: () => void;
|
|
56
|
+
cancelled?: boolean;
|
|
57
|
+
}
|
|
58
|
+
const pendingSubs: PendingSub[] = [];
|
|
59
|
+
|
|
60
|
+
// ── Core loader (memoised) ────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
let realSable: SableAPI | null = null;
|
|
63
|
+
let corePromise: Promise<SableAPI> | null = null;
|
|
64
|
+
|
|
65
|
+
function loadCore(): Promise<SableAPI> {
|
|
66
|
+
if (realSable) return Promise.resolve(realSable);
|
|
67
|
+
if (corePromise) return corePromise;
|
|
68
|
+
|
|
69
|
+
if (!scriptSrc) {
|
|
70
|
+
return Promise.reject(
|
|
71
|
+
new Error(
|
|
72
|
+
"[Sable] cannot locate SDK script URL. The loader must be loaded " +
|
|
73
|
+
'via a <script src="..."> tag (document.currentScript was null).',
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const coreUrl = new URL("./sable-core.mjs", scriptSrc).href;
|
|
79
|
+
corePromise = (async () => {
|
|
80
|
+
const mod = (await import(/* @vite-ignore */ coreUrl)) as {
|
|
81
|
+
default: SableAPI;
|
|
82
|
+
};
|
|
83
|
+
realSable = mod.default;
|
|
84
|
+
|
|
85
|
+
// Flush queued subscriptions.
|
|
86
|
+
for (const sub of pendingSubs) {
|
|
87
|
+
if (sub.cancelled) continue;
|
|
88
|
+
sub.realUnsub = realSable.on(sub.event, sub.handler);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return realSable;
|
|
92
|
+
})();
|
|
93
|
+
return corePromise;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Public API proxy ──────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
const Sable: SableAPI = {
|
|
99
|
+
version: VERSION,
|
|
100
|
+
|
|
101
|
+
async start(opts: StartOptions): Promise<void> {
|
|
102
|
+
const s = await loadCore();
|
|
103
|
+
return s.start(opts);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async stop(): Promise<void> {
|
|
107
|
+
// If the user calls stop() before start() resolved, wait for the core
|
|
108
|
+
// to finish loading so we can forward the call — otherwise we'd leave
|
|
109
|
+
// the in-flight start() orphaned.
|
|
110
|
+
if (corePromise && !realSable) await corePromise;
|
|
111
|
+
if (!realSable) return;
|
|
112
|
+
return realSable.stop();
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
on<E extends keyof SableEvents>(
|
|
116
|
+
event: E,
|
|
117
|
+
handler: SableEventHandler<E>,
|
|
118
|
+
): () => void {
|
|
119
|
+
if (realSable) {
|
|
120
|
+
return realSable.on(event, handler);
|
|
121
|
+
}
|
|
122
|
+
const sub: PendingSub = {
|
|
123
|
+
event,
|
|
124
|
+
handler: handler as SableEventHandler<keyof SableEvents>,
|
|
125
|
+
};
|
|
126
|
+
pendingSubs.push(sub);
|
|
127
|
+
return () => {
|
|
128
|
+
sub.cancelled = true;
|
|
129
|
+
if (sub.realUnsub) sub.realUnsub();
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ── Install on window ─────────────────────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// First-write-wins — matches `src/global.ts` semantics for the npm path.
|
|
137
|
+
// If the customer already has a Sable (e.g. mixed npm + script tag on the
|
|
138
|
+
// same page), we don't swap it out.
|
|
139
|
+
|
|
140
|
+
if (typeof window !== "undefined" && !window.Sable) {
|
|
141
|
+
window.Sable = Sable;
|
|
142
|
+
console.log("[Sable] loader ready", VERSION);
|
|
143
|
+
}
|
package/src/version.ts
CHANGED