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