@navai/voice-frontend 0.1.1 → 0.1.3

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
@@ -1,3 +1,8 @@
1
+ import {
2
+ Orb,
3
+ useNavaiVoiceOrbStyles
4
+ } from "./chunk-KBBRQQLK.js";
5
+
1
6
  // src/agent.ts
2
7
  import { RealtimeAgent, tool } from "@openai/agents/realtime";
3
8
  import { z } from "zod";
@@ -728,6 +733,7 @@ function emitWarnings(warnings) {
728
733
  }
729
734
  function useWebVoiceAgent(options) {
730
735
  const sessionRef = useRef(null);
736
+ const attachedRealtimeSessionRef = useRef(null);
731
737
  const runtimeConfigPromise = useMemo(
732
738
  () => resolveNavaiFrontendRuntimeConfig({
733
739
  moduleLoaders: options.moduleLoaders,
@@ -758,15 +764,61 @@ function useWebVoiceAgent(options) {
758
764
  [options.apiBaseUrl, options.env]
759
765
  );
760
766
  const [status, setStatus] = useState("idle");
767
+ const [agentVoiceState, setAgentVoiceState] = useState("idle");
761
768
  const [error, setError] = useState(null);
769
+ const setAgentVoiceStateIfChanged = useCallback((next) => {
770
+ setAgentVoiceState((current) => current === next ? current : next);
771
+ }, []);
772
+ const handleSessionAudioStart = useCallback(() => {
773
+ setAgentVoiceStateIfChanged("speaking");
774
+ }, [setAgentVoiceStateIfChanged]);
775
+ const handleSessionAudioStopped = useCallback(() => {
776
+ setAgentVoiceStateIfChanged("idle");
777
+ }, [setAgentVoiceStateIfChanged]);
778
+ const handleSessionAudioInterrupted = useCallback(() => {
779
+ setAgentVoiceStateIfChanged("idle");
780
+ }, [setAgentVoiceStateIfChanged]);
781
+ const handleSessionError = useCallback(() => {
782
+ setAgentVoiceStateIfChanged("idle");
783
+ }, [setAgentVoiceStateIfChanged]);
784
+ const detachSessionAudioListeners = useCallback(() => {
785
+ const attachedSession = attachedRealtimeSessionRef.current;
786
+ if (!attachedSession) {
787
+ return;
788
+ }
789
+ attachedSession.off("audio_start", handleSessionAudioStart);
790
+ attachedSession.off("audio_stopped", handleSessionAudioStopped);
791
+ attachedSession.off("audio_interrupted", handleSessionAudioInterrupted);
792
+ attachedSession.off("error", handleSessionError);
793
+ attachedRealtimeSessionRef.current = null;
794
+ }, [handleSessionAudioInterrupted, handleSessionAudioStart, handleSessionAudioStopped, handleSessionError]);
795
+ const attachSessionAudioListeners = useCallback(
796
+ (session) => {
797
+ detachSessionAudioListeners();
798
+ session.on("audio_start", handleSessionAudioStart);
799
+ session.on("audio_stopped", handleSessionAudioStopped);
800
+ session.on("audio_interrupted", handleSessionAudioInterrupted);
801
+ session.on("error", handleSessionError);
802
+ attachedRealtimeSessionRef.current = session;
803
+ },
804
+ [
805
+ detachSessionAudioListeners,
806
+ handleSessionAudioInterrupted,
807
+ handleSessionAudioStart,
808
+ handleSessionAudioStopped,
809
+ handleSessionError
810
+ ]
811
+ );
762
812
  const stop = useCallback(() => {
813
+ detachSessionAudioListeners();
763
814
  try {
764
815
  sessionRef.current?.close();
765
816
  } finally {
766
817
  sessionRef.current = null;
767
818
  setStatus("idle");
819
+ setAgentVoiceStateIfChanged("idle");
768
820
  }
769
- }, []);
821
+ }, [detachSessionAudioListeners, setAgentVoiceStateIfChanged]);
770
822
  useEffect(() => {
771
823
  return () => {
772
824
  stop();
@@ -778,6 +830,7 @@ function useWebVoiceAgent(options) {
778
830
  }
779
831
  setError(null);
780
832
  setStatus("connecting");
833
+ setAgentVoiceStateIfChanged("idle");
781
834
  try {
782
835
  const runtimeConfig = await runtimeConfigPromise;
783
836
  const requestPayload = runtimeConfig.modelOverride ? { model: runtimeConfig.modelOverride } : {};
@@ -792,6 +845,7 @@ function useWebVoiceAgent(options) {
792
845
  });
793
846
  emitWarnings([...runtimeConfig.warnings, ...backendFunctionsResult.warnings, ...warnings]);
794
847
  const session = new RealtimeSession(agent);
848
+ attachSessionAudioListeners(session);
795
849
  if (runtimeConfig.modelOverride) {
796
850
  await session.connect({ apiKey: secretPayload.value, model: runtimeConfig.modelOverride });
797
851
  } else {
@@ -803,28 +857,389 @@ function useWebVoiceAgent(options) {
803
857
  const message = formatError(startError);
804
858
  setError(message);
805
859
  setStatus("error");
860
+ setAgentVoiceStateIfChanged("idle");
861
+ detachSessionAudioListeners();
806
862
  try {
807
863
  sessionRef.current?.close();
808
864
  } catch {
809
865
  }
810
866
  sessionRef.current = null;
811
867
  }
812
- }, [backendClient, options.navigate, runtimeConfigPromise, status]);
868
+ }, [
869
+ attachSessionAudioListeners,
870
+ backendClient,
871
+ detachSessionAudioListeners,
872
+ options.navigate,
873
+ runtimeConfigPromise,
874
+ setAgentVoiceStateIfChanged,
875
+ status
876
+ ]);
813
877
  return {
814
878
  status,
879
+ agentVoiceState,
815
880
  error,
816
881
  isConnecting: status === "connecting",
817
882
  isConnected: status === "connected",
883
+ isAgentSpeaking: agentVoiceState === "speaking",
818
884
  start,
819
885
  stop
820
886
  };
821
887
  }
888
+
889
+ // src/orb/NavaiHeroOrb.tsx
890
+ import { useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
891
+
892
+ // src/orb/dynamic.tsx
893
+ import { lazy, Suspense, useEffect as useEffect2, useState as useState2 } from "react";
894
+ import { jsx } from "react/jsx-runtime";
895
+ function dynamic(loader, options = {}) {
896
+ const { ssr = true, loading: LoadingComponent } = options;
897
+ const LazyComponent = lazy(async () => {
898
+ const loaded = await loader();
899
+ if (typeof loaded === "function") {
900
+ return { default: loaded };
901
+ }
902
+ return loaded;
903
+ });
904
+ function DynamicComponent(props) {
905
+ const [isClientReady, setIsClientReady] = useState2(ssr);
906
+ useEffect2(() => {
907
+ if (!ssr) {
908
+ setIsClientReady(true);
909
+ }
910
+ }, [ssr]);
911
+ if (!isClientReady) {
912
+ return null;
913
+ }
914
+ const fallback = LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null;
915
+ return /* @__PURE__ */ jsx(Suspense, { fallback, children: /* @__PURE__ */ jsx(LazyComponent, { ...props }) });
916
+ }
917
+ DynamicComponent.displayName = "DynamicComponent";
918
+ return DynamicComponent;
919
+ }
920
+
921
+ // src/orb/NavaiHeroOrb.tsx
922
+ import { jsx as jsx2 } from "react/jsx-runtime";
923
+ var Orb2 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
924
+ ssr: false
925
+ });
926
+ var ORB_DELAY_MS_MIN = 0;
927
+ var ORB_DELAY_MS_MAX = 6e4;
928
+ var DEFAULT_AUTOPLAY_DELAY_MS = 9e3;
929
+ var DEFAULT_REVEAL_DELAY_MS = 5200;
930
+ function clampNavaiOrbDelayMs(value, fallback) {
931
+ const numericValue = Number.isFinite(value) ? value : fallback;
932
+ return Math.min(ORB_DELAY_MS_MAX, Math.max(ORB_DELAY_MS_MIN, numericValue));
933
+ }
934
+ function NavaiHeroOrb({
935
+ className = "",
936
+ backgroundColor = "#000000",
937
+ isAgentSpeaking = false,
938
+ hoverIntensitySpeaking = 0.66,
939
+ hoverIntensityIdle = 0.08,
940
+ revealDelayMs = DEFAULT_REVEAL_DELAY_MS,
941
+ autoplayDelayMs = DEFAULT_AUTOPLAY_DELAY_MS
942
+ }) {
943
+ useNavaiVoiceOrbStyles();
944
+ const resolvedRevealDelayMs = clampNavaiOrbDelayMs(revealDelayMs, DEFAULT_REVEAL_DELAY_MS);
945
+ const resolvedAutoplayDelayMs = clampNavaiOrbDelayMs(autoplayDelayMs, DEFAULT_AUTOPLAY_DELAY_MS);
946
+ const [isOrbReady, setIsOrbReady] = useState3(resolvedRevealDelayMs === 0);
947
+ const [isOrbAutoAnimating, setIsOrbAutoAnimating] = useState3(resolvedAutoplayDelayMs === 0);
948
+ useEffect3(() => {
949
+ if (typeof window === "undefined" || resolvedRevealDelayMs === 0) {
950
+ return;
951
+ }
952
+ const revealOrb = () => setIsOrbReady(true);
953
+ window.addEventListener("pointerdown", revealOrb, { passive: true, once: true });
954
+ window.addEventListener("touchstart", revealOrb, { passive: true, once: true });
955
+ window.addEventListener("keydown", revealOrb, { once: true });
956
+ const timeoutId = window.setTimeout(revealOrb, resolvedRevealDelayMs);
957
+ return () => {
958
+ window.removeEventListener("pointerdown", revealOrb);
959
+ window.removeEventListener("touchstart", revealOrb);
960
+ window.removeEventListener("keydown", revealOrb);
961
+ window.clearTimeout(timeoutId);
962
+ };
963
+ }, [resolvedRevealDelayMs]);
964
+ useEffect3(() => {
965
+ if (typeof window === "undefined" || resolvedAutoplayDelayMs === 0) {
966
+ return;
967
+ }
968
+ let started = false;
969
+ const startOrbAnimation = () => {
970
+ if (started) {
971
+ return;
972
+ }
973
+ started = true;
974
+ setIsOrbAutoAnimating(true);
975
+ };
976
+ if (navigator.userActivation?.hasBeenActive) {
977
+ startOrbAnimation();
978
+ }
979
+ window.addEventListener("pointerdown", startOrbAnimation, { passive: true, once: true });
980
+ window.addEventListener("touchstart", startOrbAnimation, { passive: true, once: true });
981
+ window.addEventListener("keydown", startOrbAnimation, { once: true });
982
+ const timeoutId = window.setTimeout(startOrbAnimation, resolvedAutoplayDelayMs);
983
+ return () => {
984
+ window.removeEventListener("pointerdown", startOrbAnimation);
985
+ window.removeEventListener("touchstart", startOrbAnimation);
986
+ window.removeEventListener("keydown", startOrbAnimation);
987
+ window.clearTimeout(timeoutId);
988
+ };
989
+ }, [resolvedAutoplayDelayMs]);
990
+ const orbHoverIntensity = useMemo2(() => {
991
+ return isAgentSpeaking ? hoverIntensitySpeaking : hoverIntensityIdle;
992
+ }, [hoverIntensityIdle, hoverIntensitySpeaking, isAgentSpeaking]);
993
+ if (!isOrbReady) {
994
+ return null;
995
+ }
996
+ return /* @__PURE__ */ jsx2("div", { className: ["navai-voice-orb-hero", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx2(
997
+ Orb2,
998
+ {
999
+ hoverIntensity: orbHoverIntensity,
1000
+ rotateOnHover: true,
1001
+ forceHoverState: isAgentSpeaking,
1002
+ enablePointerHover: false,
1003
+ animate: isAgentSpeaking || isOrbAutoAnimating,
1004
+ backgroundColor
1005
+ }
1006
+ ) });
1007
+ }
1008
+
1009
+ // src/orb/NavaiMiniOrbDock.tsx
1010
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1011
+ var Orb3 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
1012
+ ssr: false
1013
+ });
1014
+ function NavaiMiniOrbDock({
1015
+ className = "",
1016
+ style,
1017
+ themeMode = "dark",
1018
+ placement = "bottom-right",
1019
+ isActive = false,
1020
+ isConnected = false,
1021
+ isDisabled = false,
1022
+ isAgentSpeaking = false,
1023
+ animateOrb = true,
1024
+ backgroundColor = "#060914",
1025
+ buttonAriaLabel,
1026
+ buttonIcon,
1027
+ buttonType = "button",
1028
+ onButtonClick,
1029
+ statusMessage = "",
1030
+ isError = false,
1031
+ ariaMessage = ""
1032
+ }) {
1033
+ useNavaiVoiceOrbStyles();
1034
+ const dockClassName = ["navai-voice-orb-dock", `is-${placement}`, themeMode === "light" ? "is-light" : "", className].filter(Boolean).join(" ");
1035
+ const shouldHighlightOrb = isAgentSpeaking || isActive;
1036
+ const orbHoverIntensity = isAgentSpeaking ? 0.66 : 0.08;
1037
+ return /* @__PURE__ */ jsxs("aside", { className: dockClassName, style, children: [
1038
+ /* @__PURE__ */ jsxs("div", { className: "navai-voice-orb-wrap", children: [
1039
+ /* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-surface", shouldHighlightOrb ? "is-highlighted" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
1040
+ Orb3,
1041
+ {
1042
+ hoverIntensity: orbHoverIntensity,
1043
+ rotateOnHover: true,
1044
+ forceHoverState: isAgentSpeaking,
1045
+ enablePointerHover: false,
1046
+ animate: animateOrb,
1047
+ backgroundColor
1048
+ }
1049
+ ) }),
1050
+ /* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-button-shell", isConnected ? "is-active" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
1051
+ "button",
1052
+ {
1053
+ type: buttonType,
1054
+ className: [
1055
+ "navai-voice-orb-button",
1056
+ isConnected ? "is-active" : "",
1057
+ isActive && !isConnected ? "is-connecting" : ""
1058
+ ].filter(Boolean).join(" "),
1059
+ onClick: onButtonClick,
1060
+ disabled: isDisabled,
1061
+ "aria-label": buttonAriaLabel,
1062
+ children: buttonIcon
1063
+ }
1064
+ ) })
1065
+ ] }),
1066
+ statusMessage ? /* @__PURE__ */ jsx3("p", { className: ["navai-voice-orb-status", isError ? "is-error" : ""].filter(Boolean).join(" "), role: "status", children: statusMessage }) : null,
1067
+ /* @__PURE__ */ jsx3("span", { className: "navai-voice-orb-live", "aria-live": "polite", children: ariaMessage })
1068
+ ] });
1069
+ }
1070
+
1071
+ // src/orb/NavaiVoiceHeroOrb.tsx
1072
+ import { useEffect as useEffect4 } from "react";
1073
+
1074
+ // src/orb/NavaiVoiceOrbDock.tsx
1075
+ import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
1076
+
1077
+ // src/orb/NavaiVoiceOrbDockMicIcon.tsx
1078
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1079
+ function NavaiVoiceOrbDockMicIcon({
1080
+ isActive = false,
1081
+ size = 20
1082
+ }) {
1083
+ return /* @__PURE__ */ jsxs2(
1084
+ "svg",
1085
+ {
1086
+ width: size,
1087
+ height: size,
1088
+ viewBox: "0 0 24 24",
1089
+ fill: "none",
1090
+ stroke: "currentColor",
1091
+ strokeWidth: "2",
1092
+ strokeLinecap: "round",
1093
+ strokeLinejoin: "round",
1094
+ "aria-hidden": "true",
1095
+ className: ["navai-voice-orb-icon", isActive ? "is-pulsing" : ""].filter(Boolean).join(" "),
1096
+ children: [
1097
+ /* @__PURE__ */ jsx4("path", { d: "M12 3a3 3 0 0 0-3 3v6a3 3 0 1 0 6 0V6a3 3 0 0 0-3-3Z" }),
1098
+ /* @__PURE__ */ jsx4("path", { d: "M19 10v2a7 7 0 1 1-14 0v-2" }),
1099
+ /* @__PURE__ */ jsx4("path", { d: "M12 19v3" })
1100
+ ]
1101
+ }
1102
+ );
1103
+ }
1104
+
1105
+ // src/orb/NavaiVoiceOrbDockSpinnerIcon.tsx
1106
+ import { jsx as jsx5 } from "react/jsx-runtime";
1107
+ function NavaiVoiceOrbDockSpinnerIcon({
1108
+ size = 20
1109
+ }) {
1110
+ const style = {
1111
+ width: size,
1112
+ height: size
1113
+ };
1114
+ return /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "navai-voice-orb-spinner", style });
1115
+ }
1116
+
1117
+ // src/orb/NavaiVoiceOrbDock.tsx
1118
+ import { jsx as jsx6 } from "react/jsx-runtime";
1119
+ var DEFAULT_MESSAGES = {
1120
+ ariaStart: "Activate NAVAI voice",
1121
+ ariaStop: "Deactivate NAVAI voice",
1122
+ idle: "NAVAI ready to start.",
1123
+ connecting: "Connecting NAVAI voice...",
1124
+ listening: "NAVAI is listening.",
1125
+ speaking: "NAVAI is speaking.",
1126
+ errorPrefix: "NAVAI error"
1127
+ };
1128
+ function resolveNavaiVoiceOrbRuntimeSnapshot(agent) {
1129
+ return {
1130
+ status: agent.status,
1131
+ agentVoiceState: agent.agentVoiceState,
1132
+ isAgentSpeaking: agent.isAgentSpeaking,
1133
+ error: agent.error
1134
+ };
1135
+ }
1136
+ function resolveStatusMessage(runtimeSnapshot, messages) {
1137
+ if (runtimeSnapshot.error) {
1138
+ return `${messages.errorPrefix}: ${runtimeSnapshot.error}`;
1139
+ }
1140
+ if (runtimeSnapshot.isAgentSpeaking) {
1141
+ return messages.speaking;
1142
+ }
1143
+ if (runtimeSnapshot.status === "connecting") {
1144
+ return messages.connecting;
1145
+ }
1146
+ if (runtimeSnapshot.status === "connected") {
1147
+ return messages.listening;
1148
+ }
1149
+ return messages.idle;
1150
+ }
1151
+ function NavaiVoiceOrbDock({
1152
+ agent,
1153
+ className,
1154
+ style,
1155
+ themeMode = "dark",
1156
+ placement = "bottom-right",
1157
+ backgroundColorLight = "#f4f6fb",
1158
+ backgroundColorDark = "#060914",
1159
+ showStatus = true,
1160
+ messages
1161
+ }) {
1162
+ const resolvedMessages = useMemo3(() => ({ ...DEFAULT_MESSAGES, ...messages }), [messages]);
1163
+ const runtimeSnapshot = useMemo3(() => resolveNavaiVoiceOrbRuntimeSnapshot(agent), [agent]);
1164
+ const statusMessage = showStatus ? resolveStatusMessage(runtimeSnapshot, resolvedMessages) : "";
1165
+ const isError = runtimeSnapshot.status === "error" || Boolean(runtimeSnapshot.error);
1166
+ const isConnecting = runtimeSnapshot.status === "connecting";
1167
+ const isActive = runtimeSnapshot.status === "connecting" || runtimeSnapshot.status === "connected";
1168
+ const isDisabled = agent.isConnecting;
1169
+ const shouldAnimateOrb = runtimeSnapshot.status !== "error";
1170
+ const handleToggle = useCallback2(() => {
1171
+ if (agent.isConnecting) {
1172
+ return;
1173
+ }
1174
+ if (agent.isConnected) {
1175
+ agent.stop();
1176
+ return;
1177
+ }
1178
+ void agent.start();
1179
+ }, [agent]);
1180
+ return /* @__PURE__ */ jsx6(
1181
+ NavaiMiniOrbDock,
1182
+ {
1183
+ className,
1184
+ style,
1185
+ themeMode,
1186
+ placement,
1187
+ isActive,
1188
+ isConnected: agent.isConnected,
1189
+ isDisabled,
1190
+ isAgentSpeaking: agent.isAgentSpeaking,
1191
+ animateOrb: shouldAnimateOrb,
1192
+ backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
1193
+ buttonAriaLabel: isConnecting ? resolvedMessages.connecting : agent.isConnected ? resolvedMessages.ariaStop : resolvedMessages.ariaStart,
1194
+ buttonIcon: isConnecting ? /* @__PURE__ */ jsx6(NavaiVoiceOrbDockSpinnerIcon, {}) : /* @__PURE__ */ jsx6(NavaiVoiceOrbDockMicIcon, { isActive: agent.isConnected || agent.isAgentSpeaking }),
1195
+ onButtonClick: handleToggle,
1196
+ statusMessage,
1197
+ isError,
1198
+ ariaMessage: statusMessage
1199
+ }
1200
+ );
1201
+ }
1202
+
1203
+ // src/orb/NavaiVoiceHeroOrb.tsx
1204
+ import { jsx as jsx7 } from "react/jsx-runtime";
1205
+ function NavaiVoiceHeroOrb({
1206
+ agent,
1207
+ themeMode = "dark",
1208
+ backgroundColorLight = "#ffffff",
1209
+ backgroundColorDark = "#000000",
1210
+ onRuntimeSnapshotChange,
1211
+ ...orbProps
1212
+ }) {
1213
+ const runtimeSnapshot = resolveNavaiVoiceOrbRuntimeSnapshot(agent);
1214
+ useEffect4(() => {
1215
+ if (typeof onRuntimeSnapshotChange === "function") {
1216
+ onRuntimeSnapshotChange(runtimeSnapshot);
1217
+ }
1218
+ }, [onRuntimeSnapshotChange, runtimeSnapshot]);
1219
+ return /* @__PURE__ */ jsx7(
1220
+ NavaiHeroOrb,
1221
+ {
1222
+ ...orbProps,
1223
+ isAgentSpeaking: runtimeSnapshot.isAgentSpeaking,
1224
+ backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
1225
+ className: themeMode === "light" ? "is-light" : ""
1226
+ }
1227
+ );
1228
+ }
822
1229
  export {
1230
+ NavaiHeroOrb,
1231
+ NavaiMiniOrbDock,
1232
+ NavaiVoiceHeroOrb,
1233
+ NavaiVoiceOrbDock,
1234
+ NavaiVoiceOrbDockMicIcon,
1235
+ Orb,
823
1236
  buildNavaiAgent,
1237
+ clampNavaiOrbDelayMs,
824
1238
  createNavaiBackendClient,
825
1239
  getNavaiRoutePromptLines,
826
1240
  loadNavaiFunctions,
827
1241
  resolveNavaiFrontendRuntimeConfig,
828
1242
  resolveNavaiRoute,
1243
+ resolveNavaiVoiceOrbRuntimeSnapshot,
829
1244
  useWebVoiceAgent
830
1245
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navai/voice-frontend",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Frontend helpers to build OpenAI Realtime voice agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -20,7 +20,9 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "bin",
23
- "README.md"
23
+ "README.md",
24
+ "README.es.md",
25
+ "README.en.md"
24
26
  ],
25
27
  "scripts": {
26
28
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
@@ -30,6 +32,7 @@
30
32
  },
31
33
  "dependencies": {
32
34
  "@openai/agents": "^0.4.14",
35
+ "ogl": "^1.0.11",
33
36
  "zod": "^4.0.0"
34
37
  },
35
38
  "peerDependencies": {