Skip to content

Commit db4c6d7

Browse files
authored
feat(web-sdk): add full TypeScript definitions and improve TS support#403
1 parent 93c001f commit db4c6d7

12 files changed

Lines changed: 285 additions & 54 deletions

File tree

sdk/runanywhere-web/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,59 @@ module.exports = {
162162
163163
---
164164

165+
## TypeScript Usage
166+
167+
`@runanywhere/web` ships with full TypeScript definitions. No `@types/` package is needed.
168+
169+
```typescript
170+
import {
171+
RunAnywhere,
172+
SDKEnvironment,
173+
SDKError,
174+
SDKErrorCode,
175+
isSDKError,
176+
type SDKInitOptions, // canonical name (or InitializeOptions alias)
177+
type GenerationOptions, // canonical name (or GenerateOptions alias)
178+
type ChatMessage,
179+
type ModelDescriptor,
180+
} from '@runanywhere/web';
181+
182+
// Fully typed initialization
183+
const options: SDKInitOptions = {
184+
environment: SDKEnvironment.Development,
185+
};
186+
await RunAnywhere.initialize(options);
187+
188+
// Typed generation options (used by backend packages: LlamaCPP, ONNX)
189+
const genOptions: GenerationOptions = {
190+
systemPrompt: 'You are a helpful assistant.',
191+
maxTokens: 256,
192+
temperature: 0.7,
193+
};
194+
195+
// Typed error handling
196+
try {
197+
// ... any SDK call (e.g. loadModel, or backend TextGeneration.generate, etc.)
198+
} catch (error) {
199+
if (isSDKError(error)) {
200+
switch (error.code) {
201+
case SDKErrorCode.NotInitialized:
202+
console.error('Call RunAnywhere.initialize() first.');
203+
break;
204+
case SDKErrorCode.ModelNotLoaded:
205+
console.error('Load a model first.');
206+
break;
207+
default:
208+
console.error('SDK error:', error.message);
209+
}
210+
}
211+
}
212+
```
213+
214+
Note: `InitializeOptions` is a convenience alias for `SDKInitOptions`; `GenerateOptions` for `GenerationOptions`. Backend-specific APIs (e.g. `TextGeneration`, `STT`, `TTS`) live in `@runanywhere/web-llamacpp` and `@runanywhere/web-onnx` when using the split-package layout.
215+
216+
---
217+
165218
## Quick Start
166219

167220
### 1. Initialize the SDK

sdk/runanywhere-web/packages/core/package.json

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,44 @@
55
"type": "module",
66
"main": "./dist/index.js",
77
"module": "./dist/index.js",
8-
"types": "./dist/index.d.ts",
8+
"types": "./dist/types/index.d.ts",
99
"sideEffects": false,
1010
"exports": {
1111
".": {
12-
"import": "./dist/index.js",
13-
"types": "./dist/index.d.ts"
12+
"types": "./dist/types/index.d.ts",
13+
"import": "./dist/index.js"
1414
}
1515
},
16-
"files": [
17-
"dist/",
18-
"README.md"
19-
],
16+
"files": ["dist", "README.md"],
2017
"scripts": {
2118
"build": "tsc",
2219
"dev": "tsc --watch",
23-
"lint": "tsc --noEmit",
20+
"lint": "eslint src --max-warnings 0",
21+
"lint:ts": "tsc --noEmit",
2422
"typecheck": "tsc --noEmit",
23+
"test:types": "tsd",
24+
"test": "tsd",
25+
"test:e2e": "npm run build && (cd ../../../../examples/web/RunAnywhereAI && npm run build)",
2526
"clean": "rm -rf dist",
2627
"prepublishOnly": "test -d dist || (echo 'ERROR: Not built. Run npm run build first.' && exit 1)"
2728
},
28-
"keywords": [
29-
"runanywhere",
30-
"ai",
31-
"llm",
32-
"stt",
33-
"tts",
34-
"vad",
35-
"wasm",
36-
"webgpu",
37-
"on-device",
38-
"browser",
39-
"inference"
40-
],
29+
"keywords": ["runanywhere", "ai", "llm", "stt", "tts", "vad", "wasm", "webgpu", "on-device", "browser", "inference"],
4130
"author": "RunAnywhere AI",
4231
"license": "MIT",
43-
"repository": {
44-
"type": "git",
45-
"url": "https://github.com/RunanywhereAI/runanywhere-sdks.git",
46-
"directory": "sdk/runanywhere-web/packages/core"
47-
},
32+
"repository": {"type": "git", "url": "https://github.com/RunanywhereAI/runanywhere-sdks.git", "directory": "sdk/runanywhere-web/packages/core"},
4833
"homepage": "https://github.com/RunanywhereAI/runanywhere-sdks/tree/main/sdk/runanywhere-web",
49-
"bugs": {
50-
"url": "https://github.com/RunanywhereAI/runanywhere-sdks/issues"
51-
},
52-
"publishConfig": {
53-
"access": "public"
54-
},
55-
"engines": {
56-
"node": ">=18.0.0"
57-
},
34+
"bugs": {"url": "https://github.com/RunanywhereAI/runanywhere-sdks/issues"},
35+
"publishConfig": {"access": "public"},
36+
"engines": {"node": ">=18.0.0"},
5837
"peerDependencies": {},
38+
"tsd": {
39+
"directory": "src/__tests__"
40+
},
5941
"devDependencies": {
60-
"typescript": "^5.6.0"
42+
"typescript": "^5.6.0",
43+
"tsd": "^0.33.0",
44+
"eslint": "^9.0.0",
45+
"@eslint/js": "^9.0.0",
46+
"typescript-eslint": "^8.0.0"
6147
}
6248
}

sdk/runanywhere-web/packages/core/src/Foundation/ErrorTypes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,8 @@ export class SDKError extends Error {
119119
);
120120
}
121121
}
122+
123+
/** Type guard: returns true if the value is an SDKError instance. */
124+
export function isSDKError(error: unknown): error is SDKError {
125+
return error instanceof SDKError;
126+
}

sdk/runanywhere-web/packages/core/src/Infrastructure/LocalFileStorage.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ const logger = new SDKLogger('LocalFileStorage');
3535
// (Not yet in standard TypeScript DOM lib)
3636
// ---------------------------------------------------------------------------
3737

38-
/* eslint-disable @typescript-eslint/no-explicit-any */
3938
interface FileSystemPermissionDescriptor {
4039
mode: 'read' | 'readwrite';
4140
}
@@ -44,7 +43,6 @@ interface FileSystemHandlePermissionMethods {
4443
queryPermission(descriptor: FileSystemPermissionDescriptor): Promise<PermissionState>;
4544
requestPermission(descriptor: FileSystemPermissionDescriptor): Promise<PermissionState>;
4645
}
47-
/* eslint-enable @typescript-eslint/no-explicit-any */
4846

4947
// ---------------------------------------------------------------------------
5048
// IndexedDB Constants
@@ -128,8 +126,8 @@ export class LocalFileStorage {
128126

129127
try {
130128
// showDirectoryPicker requires user gesture (button click)
131-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
132-
this.dirHandle = await (window as any).showDirectoryPicker({
129+
const win = window as Window & { showDirectoryPicker?(options?: { mode?: 'read' | 'readwrite' }): Promise<FileSystemDirectoryHandle> };
130+
this.dirHandle = await win.showDirectoryPicker!({
133131
mode: 'readwrite',
134132
});
135133

@@ -423,8 +421,7 @@ export class LocalFileStorage {
423421

424422
const models: Array<{ id: string; sizeBytes: number; lastModified: number }> = [];
425423

426-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
427-
for await (const [name, handle] of (this.dirHandle as any).entries()) {
424+
for await (const [name, handle] of this.dirHandle.entries()) {
428425
if (handle.kind === 'file') {
429426
const file = await (handle as FileSystemFileHandle).getFile();
430427
models.push({

sdk/runanywhere-web/packages/core/src/Infrastructure/ModelManager.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,7 @@ class ModelManagerImpl {
424424
try {
425425
const root = await navigator.storage.getDirectory();
426426
const modelsDir = await root.getDirectoryHandle('models');
427-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
428-
for await (const [name, handle] of (modelsDir as any).entries()) {
427+
for await (const [name, handle] of modelsDir.entries()) {
429428
if (handle.kind === 'file' && !name.startsWith('_')) {
430429
modelCount++;
431430
const file = await (handle as FileSystemFileHandle).getFile();

sdk/runanywhere-web/packages/core/src/Infrastructure/OPFSStorage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ export class OPFSStorage {
294294

295295
const models: StoredModelInfo[] = [];
296296

297-
for await (const [name, handle] of (this.modelsDir as any).entries()) {
297+
for await (const [name, handle] of this.modelsDir.entries()) {
298298
if (handle.kind === 'file') {
299299
const file = await (handle as FileSystemFileHandle).getFile();
300300
models.push({

sdk/runanywhere-web/packages/core/src/Public/RunAnywhere.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ import { ExtensionRegistry } from '../Infrastructure/ExtensionRegistry';
2626
import { ExtensionPoint } from '../Infrastructure/ExtensionPoint';
2727
import { LocalFileStorage } from '../Infrastructure/LocalFileStorage';
2828

29+
/** Options for showOpenFilePicker. */
30+
interface OpenFilePickerOptions {
31+
types?: Array<{ description?: string; accept?: { [k: string]: string[] } }>;
32+
multiple?: boolean;
33+
}
34+
/** Window with File System Access API (showOpenFilePicker). */
35+
interface WindowWithFilePicker extends Window {
36+
showOpenFilePicker?(options?: OpenFilePickerOptions): Promise<FileSystemFileHandle[]>;
37+
}
38+
2939
const logger = new SDKLogger('RunAnywhere');
3040

3141
// ---------------------------------------------------------------------------
@@ -164,11 +174,9 @@ export const RunAnywhere = {
164174
async importModelFromPicker(options?: { modelId?: string; accept?: string[] }): Promise<string | null> {
165175
const acceptExts = options?.accept ?? ['.gguf', '.onnx', '.bin'];
166176

167-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
168177
if ('showOpenFilePicker' in window) {
169178
try {
170-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171-
const [handle] = await (window as any).showOpenFilePicker({
179+
const [handle] = await (window as WindowWithFilePicker).showOpenFilePicker!({
172180
types: [{
173181
description: 'AI Model Files',
174182
accept: { 'application/octet-stream': acceptExts },
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Type-level tests for @runanywhere/web public API.
3+
* Run with: npx tsd
4+
*/
5+
import { expectType } from 'tsd';
6+
import {
7+
RunAnywhere,
8+
SDKEnvironment,
9+
SDKError,
10+
SDKErrorCode,
11+
isSDKError,
12+
DownloadStage,
13+
type GenerateOptions,
14+
type ChatMessage,
15+
type ModelDescriptor,
16+
type DownloadProgress,
17+
type IRunAnywhere,
18+
} from '../index';
19+
20+
// InitializeOptions (SDKInitOptions) must accept environment
21+
type InitOptions = Parameters<(typeof RunAnywhere)['initialize']>[0];
22+
const opts: InitOptions = {
23+
environment: SDKEnvironment.Development,
24+
};
25+
expectType<Promise<void>>(RunAnywhere.initialize(opts));
26+
27+
// GenerateOptions.onToken must be optional
28+
const genOpts: GenerateOptions = { temperature: 0.8 };
29+
expectType<number | undefined>(genOpts.temperature);
30+
31+
// isSDKError must be a type guard
32+
const e: unknown = new SDKError(SDKErrorCode.NotInitialized, 'test');
33+
if (isSDKError(e)) {
34+
const code: SDKErrorCode = e.code;
35+
expectType<SDKErrorCode>(code);
36+
}
37+
38+
// ChatMessage role must be a union literal
39+
const msg: ChatMessage = { role: 'user', content: 'Hello' };
40+
expectType<'user' | 'assistant' | 'system'>(msg.role);
41+
42+
// role must not accept arbitrary strings
43+
// @ts-expect-error role must not accept 'admin'
44+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- intentional for type test
45+
const bad: ChatMessage = { role: 'admin', content: 'x' };
46+
47+
// ModelDescriptor and DownloadProgress are exported
48+
const desc: ModelDescriptor = {
49+
id: 'm1',
50+
name: 'Model',
51+
url: 'https://example.com/m.gguf',
52+
memoryRequirement: 1e9,
53+
};
54+
expectType<string>(desc.id);
55+
56+
const prog: DownloadProgress = {
57+
modelId: 'm1',
58+
stage: DownloadStage.Downloading,
59+
progress: 0.5,
60+
bytesDownloaded: 100,
61+
totalBytes: 200,
62+
};
63+
expectType<number>(prog.progress);
64+
65+
// IRunAnywhere must be satisfied by the RunAnywhere export
66+
const sdk: IRunAnywhere = RunAnywhere;
67+
expectType<IRunAnywhere>(sdk);

sdk/runanywhere-web/packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type { VoicePipelineCallbacks, VoicePipelineOptions, VoicePipelineTurnRes
3333
export * from './types';
3434

3535
// Foundation
36-
export { SDKError, SDKErrorCode } from './Foundation/ErrorTypes';
36+
export { SDKError, SDKErrorCode, isSDKError } from './Foundation/ErrorTypes';
3737
export { SDKLogger, LogLevel } from './Foundation/SDKLogger';
3838
export { EventBus } from './Foundation/EventBus';
3939
export type { EventListener, Unsubscribe, SDKEventEnvelope } from './Foundation/EventBus';

0 commit comments

Comments
 (0)