@aomi-labs/client 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.js CHANGED
@@ -437,10 +437,16 @@ var AomiClient = class {
437
437
  /**
438
438
  * Get available models.
439
439
  */
440
- async getModels(sessionId) {
440
+ async getModels(sessionId, options) {
441
+ var _a;
441
442
  const url = new URL("/api/control/models", this.baseUrl);
443
+ const apiKey = (_a = options == null ? void 0 : options.apiKey) != null ? _a : this.apiKey;
444
+ const headers = new Headers(withSessionHeader(sessionId));
445
+ if (apiKey) {
446
+ headers.set(API_KEY_HEADER, apiKey);
447
+ }
442
448
  const response = await fetch(url.toString(), {
443
- headers: withSessionHeader(sessionId)
449
+ headers
444
450
  });
445
451
  if (!response.ok) {
446
452
  throw new Error(`Failed to get models: HTTP ${response.status}`);
@@ -474,11 +480,490 @@ function isSystemError(event) {
474
480
  function isAsyncCallback(event) {
475
481
  return "AsyncCallback" in event;
476
482
  }
483
+
484
+ // src/event-emitter.ts
485
+ var TypedEventEmitter = class {
486
+ constructor() {
487
+ this.listeners = /* @__PURE__ */ new Map();
488
+ }
489
+ /**
490
+ * Subscribe to an event type. Returns an unsubscribe function.
491
+ */
492
+ on(type, handler) {
493
+ let set = this.listeners.get(type);
494
+ if (!set) {
495
+ set = /* @__PURE__ */ new Set();
496
+ this.listeners.set(type, set);
497
+ }
498
+ set.add(handler);
499
+ return () => {
500
+ set.delete(handler);
501
+ if (set.size === 0) {
502
+ this.listeners.delete(type);
503
+ }
504
+ };
505
+ }
506
+ /**
507
+ * Subscribe to an event type for a single emission, then auto-unsubscribe.
508
+ */
509
+ once(type, handler) {
510
+ const wrapper = ((payload) => {
511
+ unsub();
512
+ handler(payload);
513
+ });
514
+ const unsub = this.on(type, wrapper);
515
+ return unsub;
516
+ }
517
+ /**
518
+ * Emit an event to all listeners of `type` and wildcard `"*"` listeners.
519
+ */
520
+ emit(type, payload) {
521
+ const typeSet = this.listeners.get(type);
522
+ if (typeSet) {
523
+ for (const handler of typeSet) {
524
+ handler(payload);
525
+ }
526
+ }
527
+ if (type !== "*") {
528
+ const wildcardSet = this.listeners.get("*");
529
+ if (wildcardSet) {
530
+ for (const handler of wildcardSet) {
531
+ handler({ type, payload });
532
+ }
533
+ }
534
+ }
535
+ }
536
+ /**
537
+ * Remove a specific handler from an event type.
538
+ */
539
+ off(type, handler) {
540
+ const set = this.listeners.get(type);
541
+ if (set) {
542
+ set.delete(handler);
543
+ if (set.size === 0) {
544
+ this.listeners.delete(type);
545
+ }
546
+ }
547
+ }
548
+ /**
549
+ * Remove all listeners for all event types.
550
+ */
551
+ removeAllListeners() {
552
+ this.listeners.clear();
553
+ }
554
+ };
555
+
556
+ // src/event-unwrap.ts
557
+ function unwrapSystemEvent(event) {
558
+ var _a;
559
+ if (isInlineCall(event)) {
560
+ return {
561
+ type: event.InlineCall.type,
562
+ payload: (_a = event.InlineCall.payload) != null ? _a : event.InlineCall
563
+ };
564
+ }
565
+ if (isSystemNotice(event)) {
566
+ return {
567
+ type: "system_notice",
568
+ payload: { message: event.SystemNotice }
569
+ };
570
+ }
571
+ if (isSystemError(event)) {
572
+ return {
573
+ type: "system_error",
574
+ payload: { message: event.SystemError }
575
+ };
576
+ }
577
+ if (isAsyncCallback(event)) {
578
+ return {
579
+ type: "async_callback",
580
+ payload: event.AsyncCallback
581
+ };
582
+ }
583
+ return null;
584
+ }
585
+
586
+ // src/wallet-utils.ts
587
+ function asRecord(value) {
588
+ if (!value || typeof value !== "object" || Array.isArray(value))
589
+ return void 0;
590
+ return value;
591
+ }
592
+ function getToolArgs(payload) {
593
+ var _a;
594
+ const root = asRecord(payload);
595
+ const nestedArgs = asRecord(root == null ? void 0 : root.args);
596
+ return (_a = nestedArgs != null ? nestedArgs : root) != null ? _a : {};
597
+ }
598
+ function parseChainId(value) {
599
+ if (typeof value === "number" && Number.isFinite(value)) return value;
600
+ if (typeof value !== "string") return void 0;
601
+ const trimmed = value.trim();
602
+ if (!trimmed) return void 0;
603
+ if (trimmed.startsWith("0x")) {
604
+ const parsedHex = Number.parseInt(trimmed.slice(2), 16);
605
+ return Number.isFinite(parsedHex) ? parsedHex : void 0;
606
+ }
607
+ const parsed = Number.parseInt(trimmed, 10);
608
+ return Number.isFinite(parsed) ? parsed : void 0;
609
+ }
610
+ function normalizeTxPayload(payload) {
611
+ var _a, _b, _c;
612
+ const root = asRecord(payload);
613
+ const args = getToolArgs(payload);
614
+ const ctx = asRecord(root == null ? void 0 : root.ctx);
615
+ const to = typeof args.to === "string" ? args.to : void 0;
616
+ if (!to) return null;
617
+ const valueRaw = args.value;
618
+ const value = typeof valueRaw === "string" ? valueRaw : typeof valueRaw === "number" && Number.isFinite(valueRaw) ? String(Math.trunc(valueRaw)) : void 0;
619
+ const data = typeof args.data === "string" ? args.data : void 0;
620
+ const chainId = (_c = (_b = (_a = parseChainId(args.chainId)) != null ? _a : parseChainId(args.chain_id)) != null ? _b : parseChainId(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId(ctx == null ? void 0 : ctx.userChainId);
621
+ return { to, value, data, chainId };
622
+ }
623
+ function normalizeEip712Payload(payload) {
624
+ var _a;
625
+ const args = getToolArgs(payload);
626
+ const typedDataRaw = (_a = args.typed_data) != null ? _a : args.typedData;
627
+ let typedData;
628
+ if (typeof typedDataRaw === "string") {
629
+ try {
630
+ const parsed = JSON.parse(typedDataRaw);
631
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
632
+ typedData = parsed;
633
+ }
634
+ } catch (e) {
635
+ typedData = void 0;
636
+ }
637
+ } else if (typedDataRaw && typeof typedDataRaw === "object" && !Array.isArray(typedDataRaw)) {
638
+ typedData = typedDataRaw;
639
+ }
640
+ const description = typeof args.description === "string" ? args.description : void 0;
641
+ return { typed_data: typedData, description };
642
+ }
643
+
644
+ // src/session.ts
645
+ var Session = class extends TypedEventEmitter {
646
+ constructor(clientOrOptions, sessionOptions) {
647
+ var _a, _b, _c;
648
+ super();
649
+ // Internal state
650
+ this.pollTimer = null;
651
+ this.unsubscribeSSE = null;
652
+ this._isProcessing = false;
653
+ this.walletRequests = [];
654
+ this.walletRequestNextId = 1;
655
+ this._messages = [];
656
+ this.closed = false;
657
+ // For send() blocking behavior
658
+ this.pendingResolve = null;
659
+ this.client = clientOrOptions instanceof AomiClient ? clientOrOptions : new AomiClient(clientOrOptions);
660
+ this.sessionId = (_a = sessionOptions == null ? void 0 : sessionOptions.sessionId) != null ? _a : crypto.randomUUID();
661
+ this.namespace = (_b = sessionOptions == null ? void 0 : sessionOptions.namespace) != null ? _b : "default";
662
+ this.publicKey = sessionOptions == null ? void 0 : sessionOptions.publicKey;
663
+ this.apiKey = sessionOptions == null ? void 0 : sessionOptions.apiKey;
664
+ this.userState = sessionOptions == null ? void 0 : sessionOptions.userState;
665
+ this.pollIntervalMs = (_c = sessionOptions == null ? void 0 : sessionOptions.pollIntervalMs) != null ? _c : 500;
666
+ this.logger = sessionOptions == null ? void 0 : sessionOptions.logger;
667
+ this.unsubscribeSSE = this.client.subscribeSSE(
668
+ this.sessionId,
669
+ (event) => this.handleSSEEvent(event),
670
+ (error) => this.emit("error", { error })
671
+ );
672
+ }
673
+ // ===========================================================================
674
+ // Public API — Chat
675
+ // ===========================================================================
676
+ /**
677
+ * Send a message and wait for the AI to finish processing.
678
+ *
679
+ * The returned promise resolves when `is_processing` becomes `false` AND
680
+ * there are no pending wallet requests. If a wallet request arrives
681
+ * mid-processing, polling continues but the promise pauses until the
682
+ * request is resolved or rejected via `resolve()` / `reject()`.
683
+ */
684
+ async send(message) {
685
+ this.assertOpen();
686
+ const response = await this.client.sendMessage(this.sessionId, message, {
687
+ namespace: this.namespace,
688
+ publicKey: this.publicKey,
689
+ apiKey: this.apiKey,
690
+ userState: this.userState
691
+ });
692
+ this.applyState(response);
693
+ if (!response.is_processing && this.walletRequests.length === 0) {
694
+ return { messages: this._messages, title: this._title };
695
+ }
696
+ this._isProcessing = true;
697
+ this.emit("processing_start", void 0);
698
+ return new Promise((resolve) => {
699
+ this.pendingResolve = resolve;
700
+ this.startPolling();
701
+ });
702
+ }
703
+ /**
704
+ * Send a message without waiting for completion.
705
+ * Polling starts in the background; listen to events for updates.
706
+ */
707
+ async sendAsync(message) {
708
+ this.assertOpen();
709
+ const response = await this.client.sendMessage(this.sessionId, message, {
710
+ namespace: this.namespace,
711
+ publicKey: this.publicKey,
712
+ apiKey: this.apiKey,
713
+ userState: this.userState
714
+ });
715
+ this.applyState(response);
716
+ if (response.is_processing) {
717
+ this._isProcessing = true;
718
+ this.emit("processing_start", void 0);
719
+ this.startPolling();
720
+ }
721
+ return response;
722
+ }
723
+ // ===========================================================================
724
+ // Public API — Wallet Request Resolution
725
+ // ===========================================================================
726
+ /**
727
+ * Resolve a pending wallet request (transaction or EIP-712 signing).
728
+ * Sends the result to the backend and resumes polling.
729
+ */
730
+ async resolve(requestId, result) {
731
+ var _a;
732
+ const req = this.removeWalletRequest(requestId);
733
+ if (!req) {
734
+ throw new Error(`No pending wallet request with id "${requestId}"`);
735
+ }
736
+ if (req.kind === "transaction") {
737
+ await this.sendSystemEvent("wallet:tx_complete", {
738
+ txHash: (_a = result.txHash) != null ? _a : "",
739
+ status: "success",
740
+ amount: result.amount
741
+ });
742
+ } else {
743
+ const eip712Payload = req.payload;
744
+ await this.sendSystemEvent("wallet_eip712_response", {
745
+ status: "success",
746
+ signature: result.signature,
747
+ description: eip712Payload.description
748
+ });
749
+ }
750
+ if (this._isProcessing) {
751
+ this.startPolling();
752
+ }
753
+ }
754
+ /**
755
+ * Reject a pending wallet request.
756
+ * Sends an error to the backend and resumes polling.
757
+ */
758
+ async reject(requestId, reason) {
759
+ const req = this.removeWalletRequest(requestId);
760
+ if (!req) {
761
+ throw new Error(`No pending wallet request with id "${requestId}"`);
762
+ }
763
+ if (req.kind === "transaction") {
764
+ await this.sendSystemEvent("wallet:tx_complete", {
765
+ txHash: "",
766
+ status: "failed"
767
+ });
768
+ } else {
769
+ const eip712Payload = req.payload;
770
+ await this.sendSystemEvent("wallet_eip712_response", {
771
+ status: "failed",
772
+ error: reason != null ? reason : "Request rejected",
773
+ description: eip712Payload.description
774
+ });
775
+ }
776
+ if (this._isProcessing) {
777
+ this.startPolling();
778
+ }
779
+ }
780
+ // ===========================================================================
781
+ // Public API — Control
782
+ // ===========================================================================
783
+ /**
784
+ * Cancel the AI's current response.
785
+ */
786
+ async interrupt() {
787
+ this.stopPolling();
788
+ const response = await this.client.interrupt(this.sessionId);
789
+ this.applyState(response);
790
+ this._isProcessing = false;
791
+ this.emit("processing_end", void 0);
792
+ this.resolvePending();
793
+ }
794
+ /**
795
+ * Close the session. Stops polling, unsubscribes SSE, removes all listeners.
796
+ * The session cannot be used after closing.
797
+ */
798
+ close() {
799
+ var _a;
800
+ if (this.closed) return;
801
+ this.closed = true;
802
+ this.stopPolling();
803
+ (_a = this.unsubscribeSSE) == null ? void 0 : _a.call(this);
804
+ this.unsubscribeSSE = null;
805
+ this.resolvePending();
806
+ this.removeAllListeners();
807
+ }
808
+ // ===========================================================================
809
+ // Public API — Accessors
810
+ // ===========================================================================
811
+ /** Current messages in the session. */
812
+ getMessages() {
813
+ return this._messages;
814
+ }
815
+ /** Current session title. */
816
+ getTitle() {
817
+ return this._title;
818
+ }
819
+ /** Pending wallet requests waiting for resolve/reject. */
820
+ getPendingRequests() {
821
+ return [...this.walletRequests];
822
+ }
823
+ /** Whether the AI is currently processing. */
824
+ getIsProcessing() {
825
+ return this._isProcessing;
826
+ }
827
+ // ===========================================================================
828
+ // Internal — Polling (ported from PollingController)
829
+ // ===========================================================================
830
+ startPolling() {
831
+ var _a;
832
+ if (this.pollTimer || this.closed) return;
833
+ (_a = this.logger) == null ? void 0 : _a.debug("[session] polling started", this.sessionId);
834
+ this.pollTimer = setInterval(() => {
835
+ void this.pollTick();
836
+ }, this.pollIntervalMs);
837
+ }
838
+ stopPolling() {
839
+ var _a;
840
+ if (this.pollTimer) {
841
+ clearInterval(this.pollTimer);
842
+ this.pollTimer = null;
843
+ (_a = this.logger) == null ? void 0 : _a.debug("[session] polling stopped", this.sessionId);
844
+ }
845
+ }
846
+ async pollTick() {
847
+ var _a;
848
+ if (!this.pollTimer) return;
849
+ try {
850
+ const state = await this.client.fetchState(
851
+ this.sessionId,
852
+ this.userState
853
+ );
854
+ if (!this.pollTimer) return;
855
+ this.applyState(state);
856
+ if (!state.is_processing && this.walletRequests.length === 0) {
857
+ this.stopPolling();
858
+ this._isProcessing = false;
859
+ this.emit("processing_end", void 0);
860
+ this.resolvePending();
861
+ }
862
+ } catch (error) {
863
+ (_a = this.logger) == null ? void 0 : _a.debug("[session] poll error", error);
864
+ this.emit("error", { error });
865
+ }
866
+ }
867
+ // ===========================================================================
868
+ // Internal — State Application
869
+ // ===========================================================================
870
+ applyState(state) {
871
+ var _a;
872
+ if (state.messages) {
873
+ this._messages = state.messages;
874
+ this.emit("messages", this._messages);
875
+ }
876
+ if (state.title) {
877
+ this._title = state.title;
878
+ }
879
+ if ((_a = state.system_events) == null ? void 0 : _a.length) {
880
+ this.dispatchSystemEvents(state.system_events);
881
+ }
882
+ }
883
+ dispatchSystemEvents(events) {
884
+ var _a;
885
+ for (const event of events) {
886
+ const unwrapped = unwrapSystemEvent(event);
887
+ if (!unwrapped) continue;
888
+ if (unwrapped.type === "wallet_tx_request") {
889
+ const payload = normalizeTxPayload(unwrapped.payload);
890
+ if (payload) {
891
+ const req = this.enqueueWalletRequest("transaction", payload);
892
+ this.emit("wallet_tx_request", req);
893
+ }
894
+ } else if (unwrapped.type === "wallet_eip712_request") {
895
+ const payload = normalizeEip712Payload((_a = unwrapped.payload) != null ? _a : {});
896
+ const req = this.enqueueWalletRequest("eip712_sign", payload);
897
+ this.emit("wallet_eip712_request", req);
898
+ } else if (unwrapped.type === "system_notice" || unwrapped.type === "system_error" || unwrapped.type === "async_callback") {
899
+ this.emit(
900
+ unwrapped.type,
901
+ unwrapped.payload
902
+ );
903
+ }
904
+ }
905
+ }
906
+ // ===========================================================================
907
+ // Internal — SSE Handling
908
+ // ===========================================================================
909
+ handleSSEEvent(event) {
910
+ if (event.type === "title_changed" && event.new_title) {
911
+ this._title = event.new_title;
912
+ this.emit("title_changed", { title: event.new_title });
913
+ } else if (event.type === "tool_update") {
914
+ this.emit("tool_update", event);
915
+ } else if (event.type === "tool_complete") {
916
+ this.emit("tool_complete", event);
917
+ }
918
+ }
919
+ // ===========================================================================
920
+ // Internal — Wallet Request Queue
921
+ // ===========================================================================
922
+ enqueueWalletRequest(kind, payload) {
923
+ const req = {
924
+ id: `wreq-${this.walletRequestNextId++}`,
925
+ kind,
926
+ payload,
927
+ timestamp: Date.now()
928
+ };
929
+ this.walletRequests.push(req);
930
+ return req;
931
+ }
932
+ removeWalletRequest(id) {
933
+ const idx = this.walletRequests.findIndex((r) => r.id === id);
934
+ if (idx === -1) return null;
935
+ return this.walletRequests.splice(idx, 1)[0];
936
+ }
937
+ // ===========================================================================
938
+ // Internal — Helpers
939
+ // ===========================================================================
940
+ async sendSystemEvent(type, payload) {
941
+ const message = JSON.stringify({ type, payload });
942
+ await this.client.sendSystemMessage(this.sessionId, message);
943
+ }
944
+ resolvePending() {
945
+ if (this.pendingResolve) {
946
+ const resolve = this.pendingResolve;
947
+ this.pendingResolve = null;
948
+ resolve({ messages: this._messages, title: this._title });
949
+ }
950
+ }
951
+ assertOpen() {
952
+ if (this.closed) {
953
+ throw new Error("Session is closed");
954
+ }
955
+ }
956
+ };
477
957
  export {
478
958
  AomiClient,
959
+ Session,
960
+ TypedEventEmitter,
479
961
  isAsyncCallback,
480
962
  isInlineCall,
481
963
  isSystemError,
482
- isSystemNotice
964
+ isSystemNotice,
965
+ normalizeEip712Payload,
966
+ normalizeTxPayload,
967
+ unwrapSystemEvent
483
968
  };
484
969
  //# sourceMappingURL=index.js.map