@layla-network/sdk 0.1.2 → 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/dist/index.cjs CHANGED
@@ -27,7 +27,9 @@ __export(index_exports, {
27
27
  LaylaBridgeUnavailableError: () => LaylaBridgeUnavailableError,
28
28
  LaylaError: () => LaylaError,
29
29
  LaylaSDK: () => LaylaSDK,
30
- default: () => client_default
30
+ default: () => client_default,
31
+ installLaylaMock: () => installLaylaMock,
32
+ makeMockCharacter: () => makeMockCharacter
31
33
  });
32
34
  module.exports = __toCommonJS(index_exports);
33
35
 
@@ -536,13 +538,15 @@ function oneShot(command, responseEvent, extract, signal) {
536
538
 
537
539
  // src/resources/characters.ts
538
540
  var Characters = class {
539
- /**
540
- * Ask the native host for the available character cards. Resolves once with
541
- * the host's `on_get_characters_response` payload, or rejects on error/abort.
542
- */
543
- list(options = {}) {
541
+ list(offset = 0, range = 10, options = {}) {
544
542
  return oneShot(
545
- { cmd: "get_characters" },
543
+ {
544
+ cmd: "get_characters",
545
+ data: {
546
+ offset,
547
+ limit: range
548
+ }
549
+ },
546
550
  "on_get_characters_response",
547
551
  (event) => {
548
552
  const data = event.data;
@@ -578,6 +582,211 @@ var LaylaSDK = class {
578
582
  }
579
583
  };
580
584
  var client_default = LaylaSDK;
585
+
586
+ // src/mock.ts
587
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
588
+ function makeMockCharacter(name, overrides = {}) {
589
+ return {
590
+ id: `mock-${name.toLowerCase()}`,
591
+ data: {
592
+ spec: "chara_card_v2",
593
+ spec_version: "2.0",
594
+ data: {
595
+ name,
596
+ description: `${name} is a mock character for local testing.`,
597
+ personality: "friendly, helpful",
598
+ scenario: "",
599
+ first_mes: `Hi, I'm ${name}.`,
600
+ mes_example: "",
601
+ creator_notes: "Generated by layla mock.",
602
+ system_prompt: "",
603
+ post_history_instructions: "",
604
+ alternate_greetings: [],
605
+ tags: ["mock"],
606
+ creator: "layla-mock",
607
+ character_version: "1.0",
608
+ extensions: {},
609
+ ...overrides
610
+ }
611
+ }
612
+ };
613
+ }
614
+ function installLaylaMock(options = {}) {
615
+ if (typeof window === "undefined") {
616
+ throw new Error(
617
+ "installLaylaMock needs a browser-like environment (window). In Node, run your test under jsdom or happy-dom."
618
+ );
619
+ }
620
+ const latencyMs = options.latencyMs ?? 150;
621
+ const tokenDelayMs = options.tokenDelayMs ?? 40;
622
+ const errorRate = options.errorRate ?? 0;
623
+ const characters = options.characters ?? [makeMockCharacter("Aria"), makeMockCharacter("Kai")];
624
+ let current = null;
625
+ const log = (...a) => {
626
+ if (options.debug) console.log("[layla-mock]", ...a);
627
+ };
628
+ const emit = (event) => {
629
+ log("RN->web", event);
630
+ window.dispatchEvent(new MessageEvent("message", { data: JSON.stringify(event) }));
631
+ };
632
+ const emitError = (message) => emit({ event: "on_error", data: { message } });
633
+ const shouldError = () => errorRate > 0 && Math.random() < errorRate;
634
+ const tokenize = (text) => text.match(/\S+\s*/g) ?? (text ? [text] : []);
635
+ const defaultReply = (messages) => {
636
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
637
+ const said = lastUser?.content ?? "(nothing)";
638
+ return `This is a mock Layla response. You said: "${said}". Tokens stream one at a time so you can exercise your streaming UI, cancellation, and the final-content promise.`;
639
+ };
640
+ async function* tokenSource(messages) {
641
+ const produced = options.respond ? await options.respond(messages) : defaultReply(messages);
642
+ if (typeof produced === "string") {
643
+ yield* tokenize(produced);
644
+ return;
645
+ }
646
+ if (Array.isArray(produced)) {
647
+ yield* produced;
648
+ return;
649
+ }
650
+ if (produced && typeof produced === "object") {
651
+ if (Symbol.asyncIterator in produced) {
652
+ yield* produced;
653
+ return;
654
+ }
655
+ if (Symbol.iterator in produced) {
656
+ yield* produced;
657
+ }
658
+ }
659
+ }
660
+ async function handleSend(messages) {
661
+ const gen = { cancelled: false };
662
+ current = gen;
663
+ try {
664
+ await delay(latencyMs);
665
+ if (gen.cancelled) return;
666
+ if (shouldError()) {
667
+ emitError("Simulated model error");
668
+ return;
669
+ }
670
+ let snapshot = "";
671
+ for await (const delta of tokenSource(messages)) {
672
+ if (gen.cancelled) return;
673
+ snapshot += delta;
674
+ emit({ event: "on_message", data: { msg: snapshot, delta } });
675
+ await delay(tokenDelayMs);
676
+ if (gen.cancelled) return;
677
+ }
678
+ emit({ event: "on_message_end" });
679
+ } finally {
680
+ if (current === gen) current = null;
681
+ }
682
+ }
683
+ function handleCancel() {
684
+ if (current && !current.cancelled) {
685
+ current.cancelled = true;
686
+ queueMicrotask(() => emit({ event: "on_message_end" }));
687
+ }
688
+ }
689
+ async function handleGetCharacters(data) {
690
+ await delay(latencyMs);
691
+ if (shouldError()) {
692
+ emitError("Simulated characters error");
693
+ return;
694
+ }
695
+ const { offset, limit } = data;
696
+ emit({
697
+ event: "on_get_characters_response",
698
+ data: characters.slice(offset, offset + limit)
699
+ });
700
+ }
701
+ async function handleGetCharacterImage(data) {
702
+ await delay(latencyMs);
703
+ if (shouldError()) {
704
+ emitError("Simulated character image error");
705
+ return;
706
+ }
707
+ emit({
708
+ event: "on_get_character_image_response",
709
+ data: {
710
+ character_id: data.character_id,
711
+ image_data_base64: "https://picsum.photos/id/237/200/300"
712
+ // we return a url for mock, since we usually use this as the "src" of an <img> tag, it should work fine as a mock
713
+ }
714
+ });
715
+ }
716
+ async function handleGenerateImage(_) {
717
+ await delay(latencyMs);
718
+ if (shouldError()) {
719
+ emitError("Simulated image generation error");
720
+ return;
721
+ }
722
+ const totalSteps = 5;
723
+ for (let step = 1; step <= totalSteps; step++) {
724
+ await delay(latencyMs);
725
+ emit({
726
+ event: "on_generate_image_progress",
727
+ data: {
728
+ status: `Generating image... (${step}/${totalSteps})`,
729
+ steps: step,
730
+ total_steps: totalSteps
731
+ }
732
+ });
733
+ }
734
+ emit({
735
+ event: "on_generate_image_response",
736
+ data: {
737
+ image_data_base64: "https://picsum.photos/200/300"
738
+ // Placeholder image URL for the mock
739
+ }
740
+ });
741
+ }
742
+ const fakeBridge = {
743
+ postMessage(raw) {
744
+ let msg;
745
+ try {
746
+ msg = JSON.parse(raw);
747
+ } catch {
748
+ return;
749
+ }
750
+ log("web->RN", msg);
751
+ switch (msg.cmd) {
752
+ case "send_message":
753
+ void handleSend(msg.data);
754
+ break;
755
+ case "cancel":
756
+ handleCancel();
757
+ break;
758
+ case "get_characters":
759
+ void handleGetCharacters(msg.data);
760
+ break;
761
+ case "get_character_image":
762
+ void handleGetCharacterImage(msg.data);
763
+ break;
764
+ case "generate_image":
765
+ void handleGenerateImage(msg.data);
766
+ break;
767
+ default:
768
+ break;
769
+ }
770
+ }
771
+ };
772
+ const previous = window.ReactNativeWebView;
773
+ if (previous) {
774
+ log("warning: replacing an existing window.ReactNativeWebView");
775
+ }
776
+ window.ReactNativeWebView = fakeBridge;
777
+ log("installed");
778
+ let installed = true;
779
+ return {
780
+ uninstall() {
781
+ if (!installed) return;
782
+ installed = false;
783
+ if (current) current.cancelled = true;
784
+ if (previous) window.ReactNativeWebView = previous;
785
+ else delete window.ReactNativeWebView;
786
+ log("uninstalled");
787
+ }
788
+ };
789
+ }
581
790
  // Annotate the CommonJS export names for ESM import in node:
582
791
  0 && (module.exports = {
583
792
  ChatCompletionStream,
@@ -586,5 +795,7 @@ var client_default = LaylaSDK;
586
795
  LaylaAbortError,
587
796
  LaylaBridgeUnavailableError,
588
797
  LaylaError,
589
- LaylaSDK
798
+ LaylaSDK,
799
+ installLaylaMock,
800
+ makeMockCharacter
590
801
  });
package/dist/index.d.cts CHANGED
@@ -86,6 +86,10 @@ interface LaylaApiSendMessage {
86
86
  /** Ask the host for the list of available character cards (a one-shot request). */
87
87
  interface LaylaApiGetCharacters {
88
88
  cmd: 'get_characters';
89
+ data: {
90
+ offset: number;
91
+ limit: number;
92
+ };
89
93
  }
90
94
  /** Ask the host for a character image identified by <characterId>. */
91
95
  interface LaylaApiGetCharacterImage {
@@ -409,7 +413,7 @@ declare class Characters {
409
413
  * Ask the native host for the available character cards. Resolves once with
410
414
  * the host's `on_get_characters_response` payload, or rejects on error/abort.
411
415
  */
412
- list(options?: RequestOptions): Promise<LaylaCharacter[]>;
416
+ list(offset?: number, range?: number, options?: RequestOptions): Promise<LaylaCharacter[]>;
413
417
  /**
414
418
  * Ask the native host for a character portrait. Resolves to a ready-to-use
415
419
  * image src string, or null when the character has no image.
@@ -435,4 +439,53 @@ declare class LaylaSDK {
435
439
  constructor(_options?: LaylaSDKOptions);
436
440
  }
437
441
 
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 };
442
+ /**
443
+ * mock.ts
444
+ * -------
445
+ * A fake Layla native host for running the SDK OUTSIDE the app (Vite dev server,
446
+ * Storybook, a browser test, etc.). It installs a stand-in
447
+ * `window.ReactNativeWebView` and answers the web->RN commands by dispatching the
448
+ * same `MessageEvent`s the real host would, so the SDK's bridge, queue,
449
+ * streaming, and cancel paths all run for real against it.
450
+ *
451
+ * Usage (Vite — install in dev before you use the SDK):
452
+ *
453
+ * // main.ts
454
+ * if (import.meta.env.DEV) {
455
+ * const { installLaylaMock } = await import('layla-sdk/mock');
456
+ * installLaylaMock({ debug: true });
457
+ * }
458
+ *
459
+ * Install BEFORE the SDK posts its first message; otherwise the bridge sees no
460
+ * host and rejects with LaylaBridgeUnavailableError.
461
+ */
462
+
463
+ type MockReply = string | string[] | Iterable<string> | AsyncIterable<string>;
464
+ interface LaylaMockOptions {
465
+ /**
466
+ * Produce the assistant reply for a `send_message`. Return a string (which the
467
+ * mock tokenises and streams), an array of pre-split tokens, or an
468
+ * (async) iterable of tokens for full control over timing/content. May be
469
+ * async. Defaults to a canned reply that echoes the last user message.
470
+ */
471
+ respond?: (messages: LaylaChatMessage[]) => MockReply | Promise<MockReply>;
472
+ /** Cards returned by `get_characters`. Defaults to two sample cards. */
473
+ characters?: LaylaCharacter[];
474
+ /** Delay before the first event of a response (simulated latency). Default 150ms. */
475
+ latencyMs?: number;
476
+ /** Delay between streamed tokens. Default 40ms. */
477
+ tokenDelayMs?: number;
478
+ /** 0..1 probability that a request fails with on_error, for testing error paths. Default 0. */
479
+ errorRate?: number;
480
+ /** Log every web->RN command and RN->web event to the console. */
481
+ debug?: boolean;
482
+ }
483
+ interface LaylaMockHandle {
484
+ /** Remove the fake bridge and restore whatever was there before. */
485
+ uninstall(): void;
486
+ }
487
+ /** Build a valid Character Card V2 with sensible mock defaults. */
488
+ declare function makeMockCharacter(name: string, overrides?: Partial<TavernCardV2['data']>): LaylaCharacter;
489
+ declare function installLaylaMock(options?: LaylaMockOptions): LaylaMockHandle;
490
+
491
+ 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, installLaylaMock, makeMockCharacter };
package/dist/index.d.ts CHANGED
@@ -86,6 +86,10 @@ interface LaylaApiSendMessage {
86
86
  /** Ask the host for the list of available character cards (a one-shot request). */
87
87
  interface LaylaApiGetCharacters {
88
88
  cmd: 'get_characters';
89
+ data: {
90
+ offset: number;
91
+ limit: number;
92
+ };
89
93
  }
90
94
  /** Ask the host for a character image identified by <characterId>. */
91
95
  interface LaylaApiGetCharacterImage {
@@ -409,7 +413,7 @@ declare class Characters {
409
413
  * Ask the native host for the available character cards. Resolves once with
410
414
  * the host's `on_get_characters_response` payload, or rejects on error/abort.
411
415
  */
412
- list(options?: RequestOptions): Promise<LaylaCharacter[]>;
416
+ list(offset?: number, range?: number, options?: RequestOptions): Promise<LaylaCharacter[]>;
413
417
  /**
414
418
  * Ask the native host for a character portrait. Resolves to a ready-to-use
415
419
  * image src string, or null when the character has no image.
@@ -435,4 +439,53 @@ declare class LaylaSDK {
435
439
  constructor(_options?: LaylaSDKOptions);
436
440
  }
437
441
 
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 };
442
+ /**
443
+ * mock.ts
444
+ * -------
445
+ * A fake Layla native host for running the SDK OUTSIDE the app (Vite dev server,
446
+ * Storybook, a browser test, etc.). It installs a stand-in
447
+ * `window.ReactNativeWebView` and answers the web->RN commands by dispatching the
448
+ * same `MessageEvent`s the real host would, so the SDK's bridge, queue,
449
+ * streaming, and cancel paths all run for real against it.
450
+ *
451
+ * Usage (Vite — install in dev before you use the SDK):
452
+ *
453
+ * // main.ts
454
+ * if (import.meta.env.DEV) {
455
+ * const { installLaylaMock } = await import('layla-sdk/mock');
456
+ * installLaylaMock({ debug: true });
457
+ * }
458
+ *
459
+ * Install BEFORE the SDK posts its first message; otherwise the bridge sees no
460
+ * host and rejects with LaylaBridgeUnavailableError.
461
+ */
462
+
463
+ type MockReply = string | string[] | Iterable<string> | AsyncIterable<string>;
464
+ interface LaylaMockOptions {
465
+ /**
466
+ * Produce the assistant reply for a `send_message`. Return a string (which the
467
+ * mock tokenises and streams), an array of pre-split tokens, or an
468
+ * (async) iterable of tokens for full control over timing/content. May be
469
+ * async. Defaults to a canned reply that echoes the last user message.
470
+ */
471
+ respond?: (messages: LaylaChatMessage[]) => MockReply | Promise<MockReply>;
472
+ /** Cards returned by `get_characters`. Defaults to two sample cards. */
473
+ characters?: LaylaCharacter[];
474
+ /** Delay before the first event of a response (simulated latency). Default 150ms. */
475
+ latencyMs?: number;
476
+ /** Delay between streamed tokens. Default 40ms. */
477
+ tokenDelayMs?: number;
478
+ /** 0..1 probability that a request fails with on_error, for testing error paths. Default 0. */
479
+ errorRate?: number;
480
+ /** Log every web->RN command and RN->web event to the console. */
481
+ debug?: boolean;
482
+ }
483
+ interface LaylaMockHandle {
484
+ /** Remove the fake bridge and restore whatever was there before. */
485
+ uninstall(): void;
486
+ }
487
+ /** Build a valid Character Card V2 with sensible mock defaults. */
488
+ declare function makeMockCharacter(name: string, overrides?: Partial<TavernCardV2['data']>): LaylaCharacter;
489
+ declare function installLaylaMock(options?: LaylaMockOptions): LaylaMockHandle;
490
+
491
+ 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, installLaylaMock, makeMockCharacter };
package/dist/index.js CHANGED
@@ -503,13 +503,15 @@ function oneShot(command, responseEvent, extract, signal) {
503
503
 
504
504
  // src/resources/characters.ts
505
505
  var Characters = class {
506
- /**
507
- * Ask the native host for the available character cards. Resolves once with
508
- * the host's `on_get_characters_response` payload, or rejects on error/abort.
509
- */
510
- list(options = {}) {
506
+ list(offset = 0, range = 10, options = {}) {
511
507
  return oneShot(
512
- { cmd: "get_characters" },
508
+ {
509
+ cmd: "get_characters",
510
+ data: {
511
+ offset,
512
+ limit: range
513
+ }
514
+ },
513
515
  "on_get_characters_response",
514
516
  (event) => {
515
517
  const data = event.data;
@@ -545,6 +547,211 @@ var LaylaSDK = class {
545
547
  }
546
548
  };
547
549
  var client_default = LaylaSDK;
550
+
551
+ // src/mock.ts
552
+ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
553
+ function makeMockCharacter(name, overrides = {}) {
554
+ return {
555
+ id: `mock-${name.toLowerCase()}`,
556
+ data: {
557
+ spec: "chara_card_v2",
558
+ spec_version: "2.0",
559
+ data: {
560
+ name,
561
+ description: `${name} is a mock character for local testing.`,
562
+ personality: "friendly, helpful",
563
+ scenario: "",
564
+ first_mes: `Hi, I'm ${name}.`,
565
+ mes_example: "",
566
+ creator_notes: "Generated by layla mock.",
567
+ system_prompt: "",
568
+ post_history_instructions: "",
569
+ alternate_greetings: [],
570
+ tags: ["mock"],
571
+ creator: "layla-mock",
572
+ character_version: "1.0",
573
+ extensions: {},
574
+ ...overrides
575
+ }
576
+ }
577
+ };
578
+ }
579
+ function installLaylaMock(options = {}) {
580
+ if (typeof window === "undefined") {
581
+ throw new Error(
582
+ "installLaylaMock needs a browser-like environment (window). In Node, run your test under jsdom or happy-dom."
583
+ );
584
+ }
585
+ const latencyMs = options.latencyMs ?? 150;
586
+ const tokenDelayMs = options.tokenDelayMs ?? 40;
587
+ const errorRate = options.errorRate ?? 0;
588
+ const characters = options.characters ?? [makeMockCharacter("Aria"), makeMockCharacter("Kai")];
589
+ let current = null;
590
+ const log = (...a) => {
591
+ if (options.debug) console.log("[layla-mock]", ...a);
592
+ };
593
+ const emit = (event) => {
594
+ log("RN->web", event);
595
+ window.dispatchEvent(new MessageEvent("message", { data: JSON.stringify(event) }));
596
+ };
597
+ const emitError = (message) => emit({ event: "on_error", data: { message } });
598
+ const shouldError = () => errorRate > 0 && Math.random() < errorRate;
599
+ const tokenize = (text) => text.match(/\S+\s*/g) ?? (text ? [text] : []);
600
+ const defaultReply = (messages) => {
601
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
602
+ const said = lastUser?.content ?? "(nothing)";
603
+ return `This is a mock Layla response. You said: "${said}". Tokens stream one at a time so you can exercise your streaming UI, cancellation, and the final-content promise.`;
604
+ };
605
+ async function* tokenSource(messages) {
606
+ const produced = options.respond ? await options.respond(messages) : defaultReply(messages);
607
+ if (typeof produced === "string") {
608
+ yield* tokenize(produced);
609
+ return;
610
+ }
611
+ if (Array.isArray(produced)) {
612
+ yield* produced;
613
+ return;
614
+ }
615
+ if (produced && typeof produced === "object") {
616
+ if (Symbol.asyncIterator in produced) {
617
+ yield* produced;
618
+ return;
619
+ }
620
+ if (Symbol.iterator in produced) {
621
+ yield* produced;
622
+ }
623
+ }
624
+ }
625
+ async function handleSend(messages) {
626
+ const gen = { cancelled: false };
627
+ current = gen;
628
+ try {
629
+ await delay(latencyMs);
630
+ if (gen.cancelled) return;
631
+ if (shouldError()) {
632
+ emitError("Simulated model error");
633
+ return;
634
+ }
635
+ let snapshot = "";
636
+ for await (const delta of tokenSource(messages)) {
637
+ if (gen.cancelled) return;
638
+ snapshot += delta;
639
+ emit({ event: "on_message", data: { msg: snapshot, delta } });
640
+ await delay(tokenDelayMs);
641
+ if (gen.cancelled) return;
642
+ }
643
+ emit({ event: "on_message_end" });
644
+ } finally {
645
+ if (current === gen) current = null;
646
+ }
647
+ }
648
+ function handleCancel() {
649
+ if (current && !current.cancelled) {
650
+ current.cancelled = true;
651
+ queueMicrotask(() => emit({ event: "on_message_end" }));
652
+ }
653
+ }
654
+ async function handleGetCharacters(data) {
655
+ await delay(latencyMs);
656
+ if (shouldError()) {
657
+ emitError("Simulated characters error");
658
+ return;
659
+ }
660
+ const { offset, limit } = data;
661
+ emit({
662
+ event: "on_get_characters_response",
663
+ data: characters.slice(offset, offset + limit)
664
+ });
665
+ }
666
+ async function handleGetCharacterImage(data) {
667
+ await delay(latencyMs);
668
+ if (shouldError()) {
669
+ emitError("Simulated character image error");
670
+ return;
671
+ }
672
+ emit({
673
+ event: "on_get_character_image_response",
674
+ data: {
675
+ character_id: data.character_id,
676
+ image_data_base64: "https://picsum.photos/id/237/200/300"
677
+ // we return a url for mock, since we usually use this as the "src" of an <img> tag, it should work fine as a mock
678
+ }
679
+ });
680
+ }
681
+ async function handleGenerateImage(_) {
682
+ await delay(latencyMs);
683
+ if (shouldError()) {
684
+ emitError("Simulated image generation error");
685
+ return;
686
+ }
687
+ const totalSteps = 5;
688
+ for (let step = 1; step <= totalSteps; step++) {
689
+ await delay(latencyMs);
690
+ emit({
691
+ event: "on_generate_image_progress",
692
+ data: {
693
+ status: `Generating image... (${step}/${totalSteps})`,
694
+ steps: step,
695
+ total_steps: totalSteps
696
+ }
697
+ });
698
+ }
699
+ emit({
700
+ event: "on_generate_image_response",
701
+ data: {
702
+ image_data_base64: "https://picsum.photos/200/300"
703
+ // Placeholder image URL for the mock
704
+ }
705
+ });
706
+ }
707
+ const fakeBridge = {
708
+ postMessage(raw) {
709
+ let msg;
710
+ try {
711
+ msg = JSON.parse(raw);
712
+ } catch {
713
+ return;
714
+ }
715
+ log("web->RN", msg);
716
+ switch (msg.cmd) {
717
+ case "send_message":
718
+ void handleSend(msg.data);
719
+ break;
720
+ case "cancel":
721
+ handleCancel();
722
+ break;
723
+ case "get_characters":
724
+ void handleGetCharacters(msg.data);
725
+ break;
726
+ case "get_character_image":
727
+ void handleGetCharacterImage(msg.data);
728
+ break;
729
+ case "generate_image":
730
+ void handleGenerateImage(msg.data);
731
+ break;
732
+ default:
733
+ break;
734
+ }
735
+ }
736
+ };
737
+ const previous = window.ReactNativeWebView;
738
+ if (previous) {
739
+ log("warning: replacing an existing window.ReactNativeWebView");
740
+ }
741
+ window.ReactNativeWebView = fakeBridge;
742
+ log("installed");
743
+ let installed = true;
744
+ return {
745
+ uninstall() {
746
+ if (!installed) return;
747
+ installed = false;
748
+ if (current) current.cancelled = true;
749
+ if (previous) window.ReactNativeWebView = previous;
750
+ else delete window.ReactNativeWebView;
751
+ log("uninstalled");
752
+ }
753
+ };
754
+ }
548
755
  export {
549
756
  ChatCompletionStream,
550
757
  Images,
@@ -553,5 +760,7 @@ export {
553
760
  LaylaBridgeUnavailableError,
554
761
  LaylaError,
555
762
  LaylaSDK,
556
- client_default as default
763
+ client_default as default,
764
+ installLaylaMock,
765
+ makeMockCharacter
557
766
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layla-network/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Layla custom mini-apps SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",