@layla-network/sdk 0.1.0 → 0.1.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/dist/index.d.cts CHANGED
@@ -1,61 +1,128 @@
1
1
  /**
2
- * LaylaSDK
3
- * --------
4
- * A web-side client (runs INSIDE the Layla WebView) that talks to the native
5
- * Layla app over the React Native WebView bridge, exposed through an API that
6
- * mirrors the OpenAI JavaScript SDK so existing OpenAI code ports with minimal
7
- * friction.
2
+ * protocol.ts
3
+ * -----------
4
+ * The native wire contract: every type that must stay in sync with the React
5
+ * Native host. If the native side changes, this is the file that changes with
6
+ * it. Nothing here has runtime behaviour it's pure types plus the one global
7
+ * declaration for the bridge channel.
8
8
  *
9
- * The native host runs the model and streams tokens back; this SDK turns that
10
- * event stream into the OpenAI shapes (`ChatCompletion`, `ChatCompletionChunk`)
11
- * and an async-iterable stream.
12
- *
13
- * Wire protocol (must match the React Native host):
14
- * Web -> RN : { cmd: 'send_message', data: LaylaChatMessage[] }
15
- * RN -> Web : { event: 'on_message', data: { msg, delta } } (per token)
16
- * { event: 'on_message_end' } (end)
17
- * { event: 'on_error', data: { message } } (error)
18
- *
19
- * Quick start:
20
- *
21
- * import { LaylaSDK } from './layla-sdk';
22
- * const layla = new LaylaSDK();
23
- *
24
- * // Streaming (async iteration) -- the OpenAI way:
25
- * const stream = await layla.chat.completions.create({
26
- * messages: [{ role: 'user', content: 'Hello' }],
27
- * stream: true,
28
- * });
29
- * for await (const chunk of stream) {
30
- * process_token(chunk.choices[0]?.delta?.content ?? '');
31
- * }
32
- *
33
- * // Streaming (event helper) -- maps cleanly onto Layla's delta/snapshot:
34
- * const s = layla.chat.completions.stream({
35
- * messages: [{ role: 'user', content: 'Hello' }],
36
- * });
37
- * s.on('content', (delta, snapshot) => render(snapshot));
38
- * const final = await s.finalContent();
39
- *
40
- * // Non-streaming -- resolves once when the model is done:
41
- * const completion = await layla.chat.completions.create({
42
- * messages: [{ role: 'user', content: 'Hello' }],
43
- * });
44
- * console.log(completion.choices[0].message.content);
9
+ * Adding a one-shot endpoint touches this file in two spots: a command in
10
+ * `LaylaApiRequest` and a response in `LaylaApiEvent`.
45
11
  */
46
- type LaylaChatRole = 'system' | 'user' | 'assistant' | 'tool' | 'function';
12
+ declare global {
13
+ interface Window {
14
+ /** Injected by the React Native <WebView> when its `onMessage` prop is set. */
15
+ ReactNativeWebView?: {
16
+ postMessage: (message: string) => void;
17
+ };
18
+ }
19
+ }
20
+ type LaylaChatRole = 'system' | 'user' | 'assistant';
47
21
  /** An OpenAI-style chat message. */
48
22
  interface LaylaChatMessage {
49
23
  role: LaylaChatRole;
50
24
  content: string | null;
51
25
  name?: string;
52
26
  }
53
- /** Web -> RN command. */
54
- interface LaylaApiMessage {
27
+ interface LaylaCharacter {
28
+ id: string;
29
+ data: TavernCardV2;
30
+ }
31
+ /**
32
+ * A character card following the Character Card V2 spec (`chara_card_v2`), as
33
+ * returned by the host's `get_characters` handler. If your app already exports
34
+ * its own `TavernCardV2`, prefer importing that — they describe the same shape.
35
+ */
36
+ interface TavernCardV2 {
37
+ spec: 'chara_card_v2';
38
+ spec_version: '2.0';
39
+ data: {
40
+ name: string;
41
+ description: string;
42
+ personality: string;
43
+ scenario: string;
44
+ first_mes: string;
45
+ mes_example: string;
46
+ creator_notes: string;
47
+ system_prompt: string;
48
+ post_history_instructions: string;
49
+ alternate_greetings: string[];
50
+ character_book?: TavernCharacterBook;
51
+ tags: string[];
52
+ creator: string;
53
+ character_version: string;
54
+ extensions: Record<string, unknown>;
55
+ };
56
+ }
57
+ interface TavernCharacterBook {
58
+ name?: string;
59
+ description?: string;
60
+ scan_depth?: number;
61
+ token_budget?: number;
62
+ recursive_scanning?: boolean;
63
+ extensions: Record<string, unknown>;
64
+ entries: Array<{
65
+ keys: string[];
66
+ content: string;
67
+ extensions: Record<string, unknown>;
68
+ enabled: boolean;
69
+ insertion_order: number;
70
+ case_sensitive?: boolean;
71
+ name?: string;
72
+ priority?: number;
73
+ id?: number;
74
+ comment?: string;
75
+ selective?: boolean;
76
+ secondary_keys?: string[];
77
+ constant?: boolean;
78
+ position?: 'before_char' | 'after_char';
79
+ }>;
80
+ }
81
+ /** Send a conversation for completion (a streaming request). */
82
+ interface LaylaApiSendMessage {
55
83
  cmd: 'send_message';
56
84
  data: LaylaChatMessage[];
57
85
  }
58
- /** RN -> Web: a streamed token. `msg` is the full snapshot, `delta` is new. */
86
+ /** Ask the host for the list of available character cards (a one-shot request). */
87
+ interface LaylaApiGetCharacters {
88
+ cmd: 'get_characters';
89
+ }
90
+ /** Ask the host for a character image identified by <characterId>. */
91
+ interface LaylaApiGetCharacterImage {
92
+ cmd: 'get_character_image';
93
+ data: {
94
+ character_id: string;
95
+ };
96
+ }
97
+ /**
98
+ * Stop the in-flight generation.
99
+ *
100
+ * Native contract: on receiving this, the host MUST stop the current
101
+ * generation and then emit a normal `on_message_end` (or `on_error`) for that
102
+ * request. That terminating event is the acknowledgement the SDK waits for
103
+ * before starting the next queued request, so it must always be sent — even
104
+ * when generation was cut short. Tokens already posted before the host
105
+ * processes the cancel are harmless; the SDK swallows them.
106
+ */
107
+ interface LaylaApiCancel {
108
+ cmd: 'cancel';
109
+ }
110
+ /**
111
+ * Ask the host to generate an image based on the provided prompt. The host should respond with an `on_generate_image_response` event containing the generated image encoded in base64 (including the data URI prefix).
112
+ */
113
+ interface LaylaApiGenerateImage {
114
+ cmd: 'generate_image';
115
+ data: {
116
+ prompt: string;
117
+ };
118
+ }
119
+ /**
120
+ * A request command (anything that opens a job and expects events back).
121
+ * Add new one-shot commands here. `cancel` is not a request — it's a control
122
+ * signal for an already-open job — so it lives outside this union.
123
+ */
124
+ type LaylaApiRequest = LaylaApiSendMessage | LaylaApiGetCharacters | LaylaApiGetCharacterImage | LaylaApiCancel | LaylaApiGenerateImage;
125
+ /** A streamed token. `msg` is the full snapshot, `delta` is new. */
59
126
  interface LaylaApiEvent_onMsg {
60
127
  event: 'on_message';
61
128
  data: {
@@ -63,18 +130,133 @@ interface LaylaApiEvent_onMsg {
63
130
  delta: string;
64
131
  };
65
132
  }
66
- /** RN -> Web: stream finished. */
133
+ /** Stream finished. */
67
134
  interface LaylaApiEvent_onMsgEnd {
68
135
  event: 'on_message_end';
69
136
  }
70
- /** RN -> Web: error. */
137
+ /** Error. Terminates whatever request is in flight, of any kind. */
71
138
  interface LaylaApiEvent_onError {
72
139
  event: 'on_error';
73
140
  data: {
74
141
  message: string;
75
142
  };
76
143
  }
77
- type LaylaApiEvent = LaylaApiEvent_onMsg | LaylaApiEvent_onMsgEnd | LaylaApiEvent_onError;
144
+ /** The character card list for a `get_characters` request. */
145
+ interface LaylaApiEvent_onGetCharactersResponse {
146
+ event: 'on_get_characters_response';
147
+ data: LaylaCharacter[];
148
+ }
149
+ /** The character image for a `get_character_image` request, encoded in base64 (includes the data URI prefix) */
150
+ interface LaylaApiEvent_onGetCharacterImageResponse {
151
+ event: 'on_get_character_image_response';
152
+ data: {
153
+ character_id: string;
154
+ image_data_base64: string | null;
155
+ } | null;
156
+ }
157
+ /**
158
+ * The generated image for a `generate_image` request, encoded in base64 (includes the data URI prefix). If `image_data_base64` is null, it indicates that there was an error during image generation.
159
+ */
160
+ interface LaylaApiEvent_onGenerateImageResponse {
161
+ event: 'on_generate_image_response';
162
+ data: {
163
+ image_data_base64: string | null;
164
+ } | null;
165
+ }
166
+ interface LaylaApiEvent_onGenerateImageProgress {
167
+ event: 'on_generate_image_progress';
168
+ data: {
169
+ status: string;
170
+ steps: number;
171
+ total_steps: number;
172
+ };
173
+ }
174
+ type LaylaApiEvent = LaylaApiEvent_onMsg | LaylaApiEvent_onMsgEnd | LaylaApiEvent_onError | LaylaApiEvent_onGetCharactersResponse | LaylaApiEvent_onGetCharacterImageResponse | LaylaApiEvent_onGenerateImageResponse | LaylaApiEvent_onGenerateImageProgress;
175
+
176
+ /**
177
+ * errors.ts
178
+ * ---------
179
+ * The error hierarchy. Everything the SDK throws or rejects with extends
180
+ * `LaylaError`, so consumers can `catch (e) { if (e instanceof LaylaError) ... }`.
181
+ */
182
+ declare class LaylaError extends Error {
183
+ constructor(message: string);
184
+ }
185
+ declare class LaylaAbortError extends LaylaError {
186
+ constructor(message?: string);
187
+ }
188
+ declare class LaylaBridgeUnavailableError extends LaylaError {
189
+ constructor();
190
+ }
191
+
192
+ /**
193
+ * internal/one-shot.ts
194
+ * --------------------
195
+ * The reusable primitive for every request/response endpoint.
196
+ *
197
+ * `OneShotRequest` is a Deferred wearing the BridgeSink interface: you give it
198
+ * the command to send, the name of the event that answers it, and how to pull
199
+ * the result out of that event. It resolves on the response event and rejects
200
+ * on error/abort. The `oneShot` helper wires up the AbortSignal and enqueues.
201
+ * Adding a new endpoint needs neither a new class here nor any bridge change.
202
+ */
203
+
204
+ /** Shared options for one-shot requests. */
205
+ interface RequestOptions {
206
+ /** Abort the request from the consumer side. */
207
+ signal?: AbortSignal;
208
+ }
209
+
210
+ /**
211
+ * internal/bridge.ts
212
+ * ------------------
213
+ * The single low-level transport over the WebView message channel.
214
+ *
215
+ * Owns one global `message` listener and serialises requests, because the
216
+ * current protocol has no request id to correlate concurrent jobs. Every job —
217
+ * chat completions and one-shot requests alike — flows through the same queue
218
+ * and single active slot, so a one-shot waits behind an in-flight generation
219
+ * rather than racing it.
220
+ *
221
+ * The bridge is intentionally event-agnostic: it routes every parsed event to
222
+ * the active job's sink and lets the sink say when it's done. The only event it
223
+ * special-cases is `on_error`, which terminates any job. New request/response
224
+ * shapes therefore need NO changes here. If you add an `id` field to both
225
+ * `LaylaApiMessage` and `LaylaApiEvent`, this is the one place that would change
226
+ * to support true concurrency.
227
+ */
228
+
229
+ /**
230
+ * Everything the bridge needs from a job. A sink consumes the inbound event
231
+ * stream for the active request and reports when the request is complete.
232
+ */
233
+ interface BridgeSink {
234
+ /**
235
+ * Handle one parsed RN->web event (never `on_error`; the bridge routes that
236
+ * to `fail`). Return `true` when this event terminates the request, so the
237
+ * bridge can free the active slot and pump the next job. Return `false` for
238
+ * events that are not yours or that don't end the request.
239
+ */
240
+ accept(event: LaylaApiEvent): boolean;
241
+ /** Terminate the request with an error (host error, abort, missing bridge). */
242
+ fail(err: Error): void;
243
+ isClosed(): boolean;
244
+ /**
245
+ * Optional: the message to post to the host if this request is cancelled
246
+ * while in flight. Streaming generation returns `{ cmd: 'cancel' }`; one-shot
247
+ * requests return nothing (there's no generation to stop — they just close
248
+ * locally and the host's eventual response frees the slot).
249
+ */
250
+ cancelMessage?(): LaylaApiRequest | null;
251
+ }
252
+
253
+ /**
254
+ * resources/chat/types.ts
255
+ * -----------------------
256
+ * The OpenAI-shaped output and parameter types for chat completions. These are
257
+ * the SDK's public surface for chat, not the native wire protocol.
258
+ */
259
+
78
260
  interface ChatCompletionChunk {
79
261
  id: string;
80
262
  object: 'chat.completion.chunk';
@@ -121,31 +303,21 @@ interface ChatCompletionCreateParamsNonStreaming extends ChatCompletionCreatePar
121
303
  interface ChatCompletionCreateParamsStreaming extends ChatCompletionCreateParamsBase {
122
304
  stream: true;
123
305
  }
124
- declare class LaylaError extends Error {
125
- constructor(message: string);
126
- }
127
- declare class LaylaAbortError extends LaylaError {
128
- constructor(message?: string);
129
- }
130
- declare class LaylaBridgeUnavailableError extends LaylaError {
131
- constructor();
132
- }
133
- declare global {
134
- interface Window {
135
- ReactNativeWebView?: {
136
- postMessage: (message: string) => void;
137
- };
138
- }
139
- }
140
- /** Implemented by ChatCompletionStream; the bridge drives it. */
141
- interface StreamSink {
142
- handleDelta(delta: string, snapshot: string): void;
143
- handleEnd(): void;
144
- handleError(err: Error): void;
145
- isClosed(): boolean;
146
- }
306
+
307
+ /**
308
+ * resources/chat/stream.ts
309
+ * ------------------------
310
+ * ChatCompletionStream: the user-facing streaming object.
311
+ *
312
+ * - Async-iterable of ChatCompletionChunk (the OpenAI `for await` pattern)
313
+ * - Event emitter: 'content' | 'chunk' | 'end' | 'error'
314
+ * - Convenience: finalContent(), finalChatCompletion()
315
+ *
316
+ * Implements BridgeSink so the bridge can drive it from `on_message*` events.
317
+ */
318
+
147
319
  type Listener = (...args: any[]) => void;
148
- declare class ChatCompletionStream implements StreamSink, AsyncIterable<ChatCompletionChunk> {
320
+ declare class ChatCompletionStream implements BridgeSink, AsyncIterable<ChatCompletionChunk> {
149
321
  private readonly id;
150
322
  private readonly created;
151
323
  private readonly model;
@@ -165,12 +337,14 @@ declare class ChatCompletionStream implements StreamSink, AsyncIterable<ChatComp
165
337
  on(event: 'error', listener: (err: Error) => void): this;
166
338
  off(event: string, listener: Listener): this;
167
339
  private emit;
168
- handleDelta(delta: string, snapshot: string): void;
169
- handleEnd(): void;
170
- handleError(err: Error): void;
340
+ accept(event: LaylaApiEvent): boolean;
341
+ fail(err: Error): void;
171
342
  isClosed(): boolean;
343
+ cancelMessage(): LaylaApiCancel;
172
344
  /** Abort from the consumer side. */
173
345
  abort(reason?: unknown): void;
346
+ private handleDelta;
347
+ private handleEnd;
174
348
  next(): Promise<IteratorResult<ChatCompletionChunk>>;
175
349
  /** Breaking out of `for await` aborts the request, like the OpenAI SDK. */
176
350
  return(): Promise<IteratorResult<ChatCompletionChunk>>;
@@ -183,6 +357,15 @@ declare class ChatCompletionStream implements StreamSink, AsyncIterable<ChatComp
183
357
  private makeChunk;
184
358
  private buildCompletion;
185
359
  }
360
+
361
+ /**
362
+ * resources/chat/index.ts
363
+ * -----------------------
364
+ * The chat resource: `layla.chat.completions.create(...)` / `.stream(...)`,
365
+ * mirroring the OpenAI SDK shape. Re-exports the public chat types and the
366
+ * stream class for the package barrel.
367
+ */
368
+
186
369
  declare class Completions {
187
370
  create(body: ChatCompletionCreateParamsNonStreaming): Promise<ChatCompletion>;
188
371
  create(body: ChatCompletionCreateParamsStreaming): Promise<ChatCompletionStream>;
@@ -196,13 +379,60 @@ declare class Completions {
196
379
  declare class Chat {
197
380
  readonly completions: Completions;
198
381
  }
382
+
383
+ /**
384
+ * resources/images.ts
385
+ * -----------------------
386
+ * The images resource: `layla.images.generateImage()`.
387
+ *
388
+ */
389
+
390
+ declare class Images {
391
+ /**
392
+ * Ask the native host to generate an image. Resolves to a ready-to-use base64 image src string, or null if the character has no image
393
+ */
394
+ generateImage(prompt: string, onProgress: (status: string, step: number, total_step: number) => void, options?: RequestOptions): Promise<string | null>;
395
+ }
396
+
397
+ /**
398
+ * resources/characters.ts
399
+ * -----------------------
400
+ * The characters resource: `layla.characters.list()`.
401
+ *
402
+ * This is the template for any one-shot endpoint — a single `oneShot(...)` call
403
+ * giving the command, the response event name, and how to read the payload.
404
+ * Copy this file to add a new resource (e.g. `settings.ts` -> `Settings.get`).
405
+ */
406
+
407
+ declare class Characters {
408
+ /**
409
+ * Ask the native host for the available character cards. Resolves once with
410
+ * the host's `on_get_characters_response` payload, or rejects on error/abort.
411
+ */
412
+ list(options?: RequestOptions): Promise<LaylaCharacter[]>;
413
+ /**
414
+ * Ask the native host for a character portrait. Resolves to a ready-to-use
415
+ * image src string, or null when the character has no image.
416
+ */
417
+ getImage(characterId: string, options?: RequestOptions): Promise<string | null>;
418
+ }
419
+
420
+ /**
421
+ * client.ts
422
+ * ---------
423
+ * The top-level client. Composes the resources into the object users interact
424
+ * with. Add a new resource by importing it and assigning it a readonly field.
425
+ */
426
+
199
427
  interface LaylaSDKOptions {
200
428
  /** Reserved for future use (e.g. default model). */
201
429
  model?: string;
202
430
  }
203
431
  declare class LaylaSDK {
204
432
  readonly chat: Chat;
433
+ readonly characters: Characters;
434
+ readonly images: Images;
205
435
  constructor(_options?: LaylaSDKOptions);
206
436
  }
207
437
 
208
- export { type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParamsBase, type ChatCompletionCreateParamsNonStreaming, type ChatCompletionCreateParamsStreaming, ChatCompletionStream, LaylaSDK as Layla, LaylaAbortError, type LaylaApiEvent, type LaylaApiEvent_onError, type LaylaApiEvent_onMsg, type LaylaApiEvent_onMsgEnd, type LaylaApiMessage, LaylaBridgeUnavailableError, type LaylaChatMessage, type LaylaChatRole, LaylaError, LaylaSDK, type LaylaSDKOptions, LaylaSDK as default };
438
+ export { type ChatCompletion, type ChatCompletionChunk, type ChatCompletionCreateParamsBase, type ChatCompletionCreateParamsNonStreaming, type ChatCompletionCreateParamsStreaming, ChatCompletionStream, Images, LaylaSDK as Layla, LaylaAbortError, type LaylaApiCancel, type LaylaApiEvent, type LaylaApiEvent_onError, type LaylaApiEvent_onGenerateImageResponse, type LaylaApiEvent_onGetCharacterImageResponse, type LaylaApiEvent_onGetCharactersResponse, type LaylaApiEvent_onMsg, type LaylaApiEvent_onMsgEnd, type LaylaApiGenerateImage, type LaylaApiGetCharacterImage, type LaylaApiGetCharacters, type LaylaApiRequest, type LaylaApiSendMessage, LaylaBridgeUnavailableError, type LaylaCharacter, type LaylaChatMessage, type LaylaChatRole, LaylaError, LaylaSDK, type LaylaSDKOptions, type RequestOptions, type TavernCardV2, type TavernCharacterBook, LaylaSDK as default };