@halo-sdk/react 1.0.0

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/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @halo-sdk/react
2
+
3
+ Lightweight, strongly-typed React hooks for Halo SDK. They fold the S5 `HaloEvent` stream into UI state so you can show cache, cost, loop, and human-in-the-loop effects live — without a heavy client runtime.
4
+
5
+ Every hook takes a `Subscribe` function with the exact shape of `HaloAgent.observe` — pass `agent.observe` directly (same process), or a transport that relays events to the browser.
6
+
7
+ ```tsx
8
+ import { useCacheProfile, useCost, useApprovals } from "@halo-sdk/react";
9
+
10
+ function Dashboard({ subscribe }) {
11
+ const cache = useCacheProfile(subscribe); // { hitRate, grade, ... }
12
+ const cost = useCost(subscribe); // { turns, estimatedUsd, ... }
13
+ const pending = useApprovals(subscribe); // pending HITL tool calls
14
+
15
+ return (
16
+ <>
17
+ <Badge grade={cache.grade} hitRate={cache.hitRate} />
18
+ <Spend usd={cost.estimatedUsd} turns={cost.turns} />
19
+ {pending.map((p) => (
20
+ <ApprovalCard key={p.toolCallId} call={p.call} />
21
+ ))}
22
+ </>
23
+ );
24
+ }
25
+ ```
26
+
27
+ ## Hooks
28
+
29
+ - `useCacheStats` — hit/miss/write tokens + hit rate
30
+ - `useCacheProfile` — the above plus an A–F cacheability grade
31
+ - `useCost` — cumulative turns, tokens, estimated USD
32
+ - `useLoopState` — current step + status (idle/running/paused/error)
33
+ - `useApprovals` — pending HITL tool approvals
34
+ - `useHaloState` — the generic reducer hook the others build on
35
+
36
+ The reducers (`cacheStatsReducer`, `costReducer`, `loopStateReducer`, `approvalsReducer`) are pure and exported, so you can fold the same stream server-side or in tests.
@@ -0,0 +1,25 @@
1
+ import type { HaloEvent, Observer } from "@halo-sdk/core";
2
+ import { type CacheStatsState, type CostState, type LoopState, type PendingApproval } from "./reducers.js";
3
+ /**
4
+ * A subscribe function — exactly the shape of `HaloAgent.observe`. Pass
5
+ * `agent.observe` directly, or a transport that fans `HaloEvent`s to the client.
6
+ */
7
+ export type Subscribe = (observer: Observer) => () => void;
8
+ /**
9
+ * Generic hook: fold the {@link HaloEvent} stream into derived state via a pure
10
+ * reducer. The building block for all the specific hooks below.
11
+ */
12
+ export declare function useHaloState<S>(subscribe: Subscribe, reducer: (state: S, event: HaloEvent) => S, initial: () => S): S;
13
+ /** Live prefix-cache hit/miss/write tokens + hit rate. */
14
+ export declare function useCacheStats(subscribe: Subscribe): CacheStatsState;
15
+ /** Cumulative turns, tokens, and estimated USD spend. */
16
+ export declare function useCost(subscribe: Subscribe): CostState;
17
+ /** Current agent-loop step + status (idle/running/paused/error). */
18
+ export declare function useLoopState(subscribe: Subscribe): LoopState;
19
+ /** Pending human-in-the-loop tool approvals (for an approval UI). */
20
+ export declare function useApprovals(subscribe: Subscribe): PendingApproval[];
21
+ /** A cacheability grade derived live from the hit rate (A–F). */
22
+ export declare function useCacheProfile(subscribe: Subscribe): CacheStatsState & {
23
+ grade: "A" | "B" | "C" | "D" | "F";
24
+ };
25
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EASL,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC,EAC1C,OAAO,EAAE,MAAM,CAAC,GACf,CAAC,CAUH;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,CAEnE;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAEvD;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAE5D;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,EAAE,CAEpE;AAED,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,eAAe,GAAG;IACvE,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;CACpC,CAGA"}
package/dist/index.cjs ADDED
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ approvalsReducer: () => approvalsReducer,
24
+ cacheStatsReducer: () => cacheStatsReducer,
25
+ costReducer: () => costReducer,
26
+ initialApprovals: () => initialApprovals,
27
+ initialCacheStats: () => initialCacheStats,
28
+ initialCost: () => initialCost,
29
+ initialLoopState: () => initialLoopState,
30
+ loopStateReducer: () => loopStateReducer,
31
+ useApprovals: () => useApprovals,
32
+ useCacheProfile: () => useCacheProfile,
33
+ useCacheStats: () => useCacheStats,
34
+ useCost: () => useCost,
35
+ useHaloState: () => useHaloState,
36
+ useLoopState: () => useLoopState
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/hooks.ts
41
+ var import_react = require("react");
42
+
43
+ // src/reducers.ts
44
+ var initialCacheStats = () => ({
45
+ hitTokens: 0,
46
+ missTokens: 0,
47
+ writeTokens: 0,
48
+ hitRate: 0
49
+ });
50
+ function cacheStatsReducer(state, event) {
51
+ if (event.type !== "cache:hit") return state;
52
+ const hitTokens = state.hitTokens + event.hitTokens;
53
+ const missTokens = state.missTokens + event.missTokens;
54
+ const total = hitTokens + missTokens;
55
+ return {
56
+ hitTokens,
57
+ missTokens,
58
+ writeTokens: state.writeTokens,
59
+ hitRate: total > 0 ? hitTokens / total : 0
60
+ };
61
+ }
62
+ var initialCost = () => ({
63
+ turns: 0,
64
+ promptTokens: 0,
65
+ completionTokens: 0,
66
+ estimatedUsd: 0
67
+ });
68
+ function costReducer(state, event) {
69
+ switch (event.type) {
70
+ case "turn:end":
71
+ return { ...state, turns: state.turns + 1 };
72
+ case "cost":
73
+ return {
74
+ ...state,
75
+ promptTokens: state.promptTokens + event.promptTokens,
76
+ completionTokens: state.completionTokens + event.completionTokens,
77
+ estimatedUsd: state.estimatedUsd + (event.estimatedUsd ?? 0)
78
+ };
79
+ default:
80
+ return state;
81
+ }
82
+ }
83
+ var initialLoopState = () => ({ step: 0, status: "idle", lastError: null });
84
+ function loopStateReducer(state, event) {
85
+ switch (event.type) {
86
+ case "loop:step":
87
+ return { ...state, step: event.step, status: "running" };
88
+ case "loop:paused":
89
+ return { ...state, status: "paused" };
90
+ case "loop:resumed":
91
+ return { ...state, status: "running" };
92
+ case "loop:error":
93
+ return { ...state, status: "error", lastError: event.error };
94
+ case "turn:end":
95
+ return state.status === "error" ? state : { ...state, status: "idle" };
96
+ default:
97
+ return state;
98
+ }
99
+ }
100
+ var initialApprovals = () => [];
101
+ function approvalsReducer(state, event) {
102
+ switch (event.type) {
103
+ case "hitl:pending":
104
+ return [...state, { toolCallId: event.call.id, call: event.call }];
105
+ case "hitl:resolved":
106
+ return state.filter((a) => a.toolCallId !== event.toolCallId);
107
+ default:
108
+ return state;
109
+ }
110
+ }
111
+
112
+ // src/hooks.ts
113
+ function useHaloState(subscribe, reducer, initial) {
114
+ const [state, setState] = (0, import_react.useState)(initial);
115
+ const reducerRef = (0, import_react.useRef)(reducer);
116
+ reducerRef.current = reducer;
117
+ (0, import_react.useEffect)(() => {
118
+ return subscribe((event) => setState((prev) => reducerRef.current(prev, event)));
119
+ }, [subscribe]);
120
+ return state;
121
+ }
122
+ function useCacheStats(subscribe) {
123
+ return useHaloState(subscribe, cacheStatsReducer, initialCacheStats);
124
+ }
125
+ function useCost(subscribe) {
126
+ return useHaloState(subscribe, costReducer, initialCost);
127
+ }
128
+ function useLoopState(subscribe) {
129
+ return useHaloState(subscribe, loopStateReducer, initialLoopState);
130
+ }
131
+ function useApprovals(subscribe) {
132
+ return useHaloState(subscribe, approvalsReducer, initialApprovals);
133
+ }
134
+ function useCacheProfile(subscribe) {
135
+ const stats = useCacheStats(subscribe);
136
+ return { ...stats, grade: gradeFor(stats.hitRate) };
137
+ }
138
+ function gradeFor(hitRate) {
139
+ if (hitRate >= 0.9) return "A";
140
+ if (hitRate >= 0.75) return "B";
141
+ if (hitRate >= 0.5) return "C";
142
+ if (hitRate >= 0.25) return "D";
143
+ return "F";
144
+ }
145
+ // Annotate the CommonJS export names for ESM import in node:
146
+ 0 && (module.exports = {
147
+ approvalsReducer,
148
+ cacheStatsReducer,
149
+ costReducer,
150
+ initialApprovals,
151
+ initialCacheStats,
152
+ initialCost,
153
+ initialLoopState,
154
+ loopStateReducer,
155
+ useApprovals,
156
+ useCacheProfile,
157
+ useCacheStats,
158
+ useCost,
159
+ useHaloState,
160
+ useLoopState
161
+ });
162
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/hooks.ts","../src/reducers.ts"],"sourcesContent":["export {\n useHaloState,\n useCacheStats,\n useCost,\n useLoopState,\n useApprovals,\n useCacheProfile,\n type Subscribe,\n} from \"./hooks.js\";\nexport {\n cacheStatsReducer,\n costReducer,\n loopStateReducer,\n approvalsReducer,\n initialCacheStats,\n initialCost,\n initialLoopState,\n initialApprovals,\n type CacheStatsState,\n type CostState,\n type LoopState,\n type PendingApproval,\n} from \"./reducers.js\";\n","import { useEffect, useRef, useState } from \"react\";\nimport type { HaloEvent, Observer } from \"@halo-sdk/core\";\nimport {\n approvalsReducer,\n cacheStatsReducer,\n costReducer,\n initialApprovals,\n initialCacheStats,\n initialCost,\n initialLoopState,\n loopStateReducer,\n type CacheStatsState,\n type CostState,\n type LoopState,\n type PendingApproval,\n} from \"./reducers.js\";\n\n/**\n * A subscribe function — exactly the shape of `HaloAgent.observe`. Pass\n * `agent.observe` directly, or a transport that fans `HaloEvent`s to the client.\n */\nexport type Subscribe = (observer: Observer) => () => void;\n\n/**\n * Generic hook: fold the {@link HaloEvent} stream into derived state via a pure\n * reducer. The building block for all the specific hooks below.\n */\nexport function useHaloState<S>(\n subscribe: Subscribe,\n reducer: (state: S, event: HaloEvent) => S,\n initial: () => S,\n): S {\n const [state, setState] = useState<S>(initial);\n const reducerRef = useRef(reducer);\n reducerRef.current = reducer;\n\n useEffect(() => {\n return subscribe((event) => setState((prev) => reducerRef.current(prev, event)));\n }, [subscribe]);\n\n return state;\n}\n\n/** Live prefix-cache hit/miss/write tokens + hit rate. */\nexport function useCacheStats(subscribe: Subscribe): CacheStatsState {\n return useHaloState(subscribe, cacheStatsReducer, initialCacheStats);\n}\n\n/** Cumulative turns, tokens, and estimated USD spend. */\nexport function useCost(subscribe: Subscribe): CostState {\n return useHaloState(subscribe, costReducer, initialCost);\n}\n\n/** Current agent-loop step + status (idle/running/paused/error). */\nexport function useLoopState(subscribe: Subscribe): LoopState {\n return useHaloState(subscribe, loopStateReducer, initialLoopState);\n}\n\n/** Pending human-in-the-loop tool approvals (for an approval UI). */\nexport function useApprovals(subscribe: Subscribe): PendingApproval[] {\n return useHaloState(subscribe, approvalsReducer, initialApprovals);\n}\n\n/** A cacheability grade derived live from the hit rate (A–F). */\nexport function useCacheProfile(subscribe: Subscribe): CacheStatsState & {\n grade: \"A\" | \"B\" | \"C\" | \"D\" | \"F\";\n} {\n const stats = useCacheStats(subscribe);\n return { ...stats, grade: gradeFor(stats.hitRate) };\n}\n\nfunction gradeFor(hitRate: number): \"A\" | \"B\" | \"C\" | \"D\" | \"F\" {\n if (hitRate >= 0.9) return \"A\";\n if (hitRate >= 0.75) return \"B\";\n if (hitRate >= 0.5) return \"C\";\n if (hitRate >= 0.25) return \"D\";\n return \"F\";\n}\n","import type { HaloEvent, ToolCall } from \"@halo-sdk/core\";\n\n// Pure reducers over the HaloEvent stream. Framework-free and unit-testable;\n// the React hooks in `hooks.ts` are thin wrappers around these.\n\nexport interface CacheStatsState {\n hitTokens: number;\n missTokens: number;\n writeTokens: number;\n hitRate: number;\n}\n\nexport const initialCacheStats = (): CacheStatsState => ({\n hitTokens: 0,\n missTokens: 0,\n writeTokens: 0,\n hitRate: 0,\n});\n\nexport function cacheStatsReducer(state: CacheStatsState, event: HaloEvent): CacheStatsState {\n if (event.type !== \"cache:hit\") return state;\n const hitTokens = state.hitTokens + event.hitTokens;\n const missTokens = state.missTokens + event.missTokens;\n const total = hitTokens + missTokens;\n return {\n hitTokens,\n missTokens,\n writeTokens: state.writeTokens,\n hitRate: total > 0 ? hitTokens / total : 0,\n };\n}\n\nexport interface CostState {\n turns: number;\n promptTokens: number;\n completionTokens: number;\n estimatedUsd: number;\n}\n\nexport const initialCost = (): CostState => ({\n turns: 0,\n promptTokens: 0,\n completionTokens: 0,\n estimatedUsd: 0,\n});\n\nexport function costReducer(state: CostState, event: HaloEvent): CostState {\n switch (event.type) {\n case \"turn:end\":\n return { ...state, turns: state.turns + 1 };\n case \"cost\":\n return {\n ...state,\n promptTokens: state.promptTokens + event.promptTokens,\n completionTokens: state.completionTokens + event.completionTokens,\n estimatedUsd: state.estimatedUsd + (event.estimatedUsd ?? 0),\n };\n default:\n return state;\n }\n}\n\nexport interface LoopState {\n step: number;\n status: \"idle\" | \"running\" | \"paused\" | \"error\";\n lastError: string | null;\n}\n\nexport const initialLoopState = (): LoopState => ({ step: 0, status: \"idle\", lastError: null });\n\nexport function loopStateReducer(state: LoopState, event: HaloEvent): LoopState {\n switch (event.type) {\n case \"loop:step\":\n return { ...state, step: event.step, status: \"running\" };\n case \"loop:paused\":\n return { ...state, status: \"paused\" };\n case \"loop:resumed\":\n return { ...state, status: \"running\" };\n case \"loop:error\":\n return { ...state, status: \"error\", lastError: event.error };\n case \"turn:end\":\n return state.status === \"error\" ? state : { ...state, status: \"idle\" };\n default:\n return state;\n }\n}\n\nexport interface PendingApproval {\n toolCallId: string;\n call: ToolCall;\n}\n\nexport const initialApprovals = (): PendingApproval[] => [];\n\nexport function approvalsReducer(state: PendingApproval[], event: HaloEvent): PendingApproval[] {\n switch (event.type) {\n case \"hitl:pending\":\n return [...state, { toolCallId: event.call.id, call: event.call }];\n case \"hitl:resolved\":\n return state.filter((a) => a.toolCallId !== event.toolCallId);\n default:\n return state;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,3 @@
1
+ export { useHaloState, useCacheStats, useCost, useLoopState, useApprovals, useCacheProfile, type Subscribe, } from "./hooks.js";
2
+ export { cacheStatsReducer, costReducer, loopStateReducer, approvalsReducer, initialCacheStats, initialCost, initialLoopState, initialApprovals, type CacheStatsState, type CostState, type LoopState, type PendingApproval, } from "./reducers.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ // src/hooks.ts
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ // src/reducers.ts
5
+ var initialCacheStats = () => ({
6
+ hitTokens: 0,
7
+ missTokens: 0,
8
+ writeTokens: 0,
9
+ hitRate: 0
10
+ });
11
+ function cacheStatsReducer(state, event) {
12
+ if (event.type !== "cache:hit") return state;
13
+ const hitTokens = state.hitTokens + event.hitTokens;
14
+ const missTokens = state.missTokens + event.missTokens;
15
+ const total = hitTokens + missTokens;
16
+ return {
17
+ hitTokens,
18
+ missTokens,
19
+ writeTokens: state.writeTokens,
20
+ hitRate: total > 0 ? hitTokens / total : 0
21
+ };
22
+ }
23
+ var initialCost = () => ({
24
+ turns: 0,
25
+ promptTokens: 0,
26
+ completionTokens: 0,
27
+ estimatedUsd: 0
28
+ });
29
+ function costReducer(state, event) {
30
+ switch (event.type) {
31
+ case "turn:end":
32
+ return { ...state, turns: state.turns + 1 };
33
+ case "cost":
34
+ return {
35
+ ...state,
36
+ promptTokens: state.promptTokens + event.promptTokens,
37
+ completionTokens: state.completionTokens + event.completionTokens,
38
+ estimatedUsd: state.estimatedUsd + (event.estimatedUsd ?? 0)
39
+ };
40
+ default:
41
+ return state;
42
+ }
43
+ }
44
+ var initialLoopState = () => ({ step: 0, status: "idle", lastError: null });
45
+ function loopStateReducer(state, event) {
46
+ switch (event.type) {
47
+ case "loop:step":
48
+ return { ...state, step: event.step, status: "running" };
49
+ case "loop:paused":
50
+ return { ...state, status: "paused" };
51
+ case "loop:resumed":
52
+ return { ...state, status: "running" };
53
+ case "loop:error":
54
+ return { ...state, status: "error", lastError: event.error };
55
+ case "turn:end":
56
+ return state.status === "error" ? state : { ...state, status: "idle" };
57
+ default:
58
+ return state;
59
+ }
60
+ }
61
+ var initialApprovals = () => [];
62
+ function approvalsReducer(state, event) {
63
+ switch (event.type) {
64
+ case "hitl:pending":
65
+ return [...state, { toolCallId: event.call.id, call: event.call }];
66
+ case "hitl:resolved":
67
+ return state.filter((a) => a.toolCallId !== event.toolCallId);
68
+ default:
69
+ return state;
70
+ }
71
+ }
72
+
73
+ // src/hooks.ts
74
+ function useHaloState(subscribe, reducer, initial) {
75
+ const [state, setState] = useState(initial);
76
+ const reducerRef = useRef(reducer);
77
+ reducerRef.current = reducer;
78
+ useEffect(() => {
79
+ return subscribe((event) => setState((prev) => reducerRef.current(prev, event)));
80
+ }, [subscribe]);
81
+ return state;
82
+ }
83
+ function useCacheStats(subscribe) {
84
+ return useHaloState(subscribe, cacheStatsReducer, initialCacheStats);
85
+ }
86
+ function useCost(subscribe) {
87
+ return useHaloState(subscribe, costReducer, initialCost);
88
+ }
89
+ function useLoopState(subscribe) {
90
+ return useHaloState(subscribe, loopStateReducer, initialLoopState);
91
+ }
92
+ function useApprovals(subscribe) {
93
+ return useHaloState(subscribe, approvalsReducer, initialApprovals);
94
+ }
95
+ function useCacheProfile(subscribe) {
96
+ const stats = useCacheStats(subscribe);
97
+ return { ...stats, grade: gradeFor(stats.hitRate) };
98
+ }
99
+ function gradeFor(hitRate) {
100
+ if (hitRate >= 0.9) return "A";
101
+ if (hitRate >= 0.75) return "B";
102
+ if (hitRate >= 0.5) return "C";
103
+ if (hitRate >= 0.25) return "D";
104
+ return "F";
105
+ }
106
+ export {
107
+ approvalsReducer,
108
+ cacheStatsReducer,
109
+ costReducer,
110
+ initialApprovals,
111
+ initialCacheStats,
112
+ initialCost,
113
+ initialLoopState,
114
+ loopStateReducer,
115
+ useApprovals,
116
+ useCacheProfile,
117
+ useCacheStats,
118
+ useCost,
119
+ useHaloState,
120
+ useLoopState
121
+ };
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks.ts","../src/reducers.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { HaloEvent, Observer } from \"@halo-sdk/core\";\nimport {\n approvalsReducer,\n cacheStatsReducer,\n costReducer,\n initialApprovals,\n initialCacheStats,\n initialCost,\n initialLoopState,\n loopStateReducer,\n type CacheStatsState,\n type CostState,\n type LoopState,\n type PendingApproval,\n} from \"./reducers.js\";\n\n/**\n * A subscribe function — exactly the shape of `HaloAgent.observe`. Pass\n * `agent.observe` directly, or a transport that fans `HaloEvent`s to the client.\n */\nexport type Subscribe = (observer: Observer) => () => void;\n\n/**\n * Generic hook: fold the {@link HaloEvent} stream into derived state via a pure\n * reducer. The building block for all the specific hooks below.\n */\nexport function useHaloState<S>(\n subscribe: Subscribe,\n reducer: (state: S, event: HaloEvent) => S,\n initial: () => S,\n): S {\n const [state, setState] = useState<S>(initial);\n const reducerRef = useRef(reducer);\n reducerRef.current = reducer;\n\n useEffect(() => {\n return subscribe((event) => setState((prev) => reducerRef.current(prev, event)));\n }, [subscribe]);\n\n return state;\n}\n\n/** Live prefix-cache hit/miss/write tokens + hit rate. */\nexport function useCacheStats(subscribe: Subscribe): CacheStatsState {\n return useHaloState(subscribe, cacheStatsReducer, initialCacheStats);\n}\n\n/** Cumulative turns, tokens, and estimated USD spend. */\nexport function useCost(subscribe: Subscribe): CostState {\n return useHaloState(subscribe, costReducer, initialCost);\n}\n\n/** Current agent-loop step + status (idle/running/paused/error). */\nexport function useLoopState(subscribe: Subscribe): LoopState {\n return useHaloState(subscribe, loopStateReducer, initialLoopState);\n}\n\n/** Pending human-in-the-loop tool approvals (for an approval UI). */\nexport function useApprovals(subscribe: Subscribe): PendingApproval[] {\n return useHaloState(subscribe, approvalsReducer, initialApprovals);\n}\n\n/** A cacheability grade derived live from the hit rate (A–F). */\nexport function useCacheProfile(subscribe: Subscribe): CacheStatsState & {\n grade: \"A\" | \"B\" | \"C\" | \"D\" | \"F\";\n} {\n const stats = useCacheStats(subscribe);\n return { ...stats, grade: gradeFor(stats.hitRate) };\n}\n\nfunction gradeFor(hitRate: number): \"A\" | \"B\" | \"C\" | \"D\" | \"F\" {\n if (hitRate >= 0.9) return \"A\";\n if (hitRate >= 0.75) return \"B\";\n if (hitRate >= 0.5) return \"C\";\n if (hitRate >= 0.25) return \"D\";\n return \"F\";\n}\n","import type { HaloEvent, ToolCall } from \"@halo-sdk/core\";\n\n// Pure reducers over the HaloEvent stream. Framework-free and unit-testable;\n// the React hooks in `hooks.ts` are thin wrappers around these.\n\nexport interface CacheStatsState {\n hitTokens: number;\n missTokens: number;\n writeTokens: number;\n hitRate: number;\n}\n\nexport const initialCacheStats = (): CacheStatsState => ({\n hitTokens: 0,\n missTokens: 0,\n writeTokens: 0,\n hitRate: 0,\n});\n\nexport function cacheStatsReducer(state: CacheStatsState, event: HaloEvent): CacheStatsState {\n if (event.type !== \"cache:hit\") return state;\n const hitTokens = state.hitTokens + event.hitTokens;\n const missTokens = state.missTokens + event.missTokens;\n const total = hitTokens + missTokens;\n return {\n hitTokens,\n missTokens,\n writeTokens: state.writeTokens,\n hitRate: total > 0 ? hitTokens / total : 0,\n };\n}\n\nexport interface CostState {\n turns: number;\n promptTokens: number;\n completionTokens: number;\n estimatedUsd: number;\n}\n\nexport const initialCost = (): CostState => ({\n turns: 0,\n promptTokens: 0,\n completionTokens: 0,\n estimatedUsd: 0,\n});\n\nexport function costReducer(state: CostState, event: HaloEvent): CostState {\n switch (event.type) {\n case \"turn:end\":\n return { ...state, turns: state.turns + 1 };\n case \"cost\":\n return {\n ...state,\n promptTokens: state.promptTokens + event.promptTokens,\n completionTokens: state.completionTokens + event.completionTokens,\n estimatedUsd: state.estimatedUsd + (event.estimatedUsd ?? 0),\n };\n default:\n return state;\n }\n}\n\nexport interface LoopState {\n step: number;\n status: \"idle\" | \"running\" | \"paused\" | \"error\";\n lastError: string | null;\n}\n\nexport const initialLoopState = (): LoopState => ({ step: 0, status: \"idle\", lastError: null });\n\nexport function loopStateReducer(state: LoopState, event: HaloEvent): LoopState {\n switch (event.type) {\n case \"loop:step\":\n return { ...state, step: event.step, status: \"running\" };\n case \"loop:paused\":\n return { ...state, status: \"paused\" };\n case \"loop:resumed\":\n return { ...state, status: \"running\" };\n case \"loop:error\":\n return { ...state, status: \"error\", lastError: event.error };\n case \"turn:end\":\n return state.status === \"error\" ? state : { ...state, status: \"idle\" };\n default:\n return state;\n }\n}\n\nexport interface PendingApproval {\n toolCallId: string;\n call: ToolCall;\n}\n\nexport const initialApprovals = (): PendingApproval[] => [];\n\nexport function approvalsReducer(state: PendingApproval[], event: HaloEvent): PendingApproval[] {\n switch (event.type) {\n case \"hitl:pending\":\n return [...state, { toolCallId: event.call.id, call: event.call }];\n case \"hitl:resolved\":\n return state.filter((a) => a.toolCallId !== event.toolCallId);\n default:\n return state;\n }\n}\n"],"mappings":";AAAA,SAAS,WAAW,QAAQ,gBAAgB;;;ACYrC,IAAM,oBAAoB,OAAwB;AAAA,EACvD,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AACX;AAEO,SAAS,kBAAkB,OAAwB,OAAmC;AAC3F,MAAI,MAAM,SAAS,YAAa,QAAO;AACvC,QAAM,YAAY,MAAM,YAAY,MAAM;AAC1C,QAAM,aAAa,MAAM,aAAa,MAAM;AAC5C,QAAM,QAAQ,YAAY;AAC1B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,aAAa,MAAM;AAAA,IACnB,SAAS,QAAQ,IAAI,YAAY,QAAQ;AAAA,EAC3C;AACF;AASO,IAAM,cAAc,OAAkB;AAAA,EAC3C,OAAO;AAAA,EACP,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAChB;AAEO,SAAS,YAAY,OAAkB,OAA6B;AACzE,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,OAAO,MAAM,QAAQ,EAAE;AAAA,IAC5C,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,cAAc,MAAM,eAAe,MAAM;AAAA,QACzC,kBAAkB,MAAM,mBAAmB,MAAM;AAAA,QACjD,cAAc,MAAM,gBAAgB,MAAM,gBAAgB;AAAA,MAC5D;AAAA,IACF;AACE,aAAO;AAAA,EACX;AACF;AAQO,IAAM,mBAAmB,OAAkB,EAAE,MAAM,GAAG,QAAQ,QAAQ,WAAW,KAAK;AAEtF,SAAS,iBAAiB,OAAkB,OAA6B;AAC9E,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,MAAM,MAAM,MAAM,QAAQ,UAAU;AAAA,IACzD,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,SAAS;AAAA,IACtC,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,UAAU;AAAA,IACvC,KAAK;AACH,aAAO,EAAE,GAAG,OAAO,QAAQ,SAAS,WAAW,MAAM,MAAM;AAAA,IAC7D,KAAK;AACH,aAAO,MAAM,WAAW,UAAU,QAAQ,EAAE,GAAG,OAAO,QAAQ,OAAO;AAAA,IACvE;AACE,aAAO;AAAA,EACX;AACF;AAOO,IAAM,mBAAmB,MAAyB,CAAC;AAEnD,SAAS,iBAAiB,OAA0B,OAAqC;AAC9F,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,KAAK,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA,IACnE,KAAK;AACH,aAAO,MAAM,OAAO,CAAC,MAAM,EAAE,eAAe,MAAM,UAAU;AAAA,IAC9D;AACE,aAAO;AAAA,EACX;AACF;;;AD5EO,SAAS,aACd,WACA,SACA,SACG;AACH,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAY,OAAO;AAC7C,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,WAAO,UAAU,CAAC,UAAU,SAAS,CAAC,SAAS,WAAW,QAAQ,MAAM,KAAK,CAAC,CAAC;AAAA,EACjF,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO;AACT;AAGO,SAAS,cAAc,WAAuC;AACnE,SAAO,aAAa,WAAW,mBAAmB,iBAAiB;AACrE;AAGO,SAAS,QAAQ,WAAiC;AACvD,SAAO,aAAa,WAAW,aAAa,WAAW;AACzD;AAGO,SAAS,aAAa,WAAiC;AAC5D,SAAO,aAAa,WAAW,kBAAkB,gBAAgB;AACnE;AAGO,SAAS,aAAa,WAAyC;AACpE,SAAO,aAAa,WAAW,kBAAkB,gBAAgB;AACnE;AAGO,SAAS,gBAAgB,WAE9B;AACA,QAAM,QAAQ,cAAc,SAAS;AACrC,SAAO,EAAE,GAAG,OAAO,OAAO,SAAS,MAAM,OAAO,EAAE;AACpD;AAEA,SAAS,SAAS,SAA8C;AAC9D,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO;AACT;","names":[]}
@@ -0,0 +1,31 @@
1
+ import type { HaloEvent, ToolCall } from "@halo-sdk/core";
2
+ export interface CacheStatsState {
3
+ hitTokens: number;
4
+ missTokens: number;
5
+ writeTokens: number;
6
+ hitRate: number;
7
+ }
8
+ export declare const initialCacheStats: () => CacheStatsState;
9
+ export declare function cacheStatsReducer(state: CacheStatsState, event: HaloEvent): CacheStatsState;
10
+ export interface CostState {
11
+ turns: number;
12
+ promptTokens: number;
13
+ completionTokens: number;
14
+ estimatedUsd: number;
15
+ }
16
+ export declare const initialCost: () => CostState;
17
+ export declare function costReducer(state: CostState, event: HaloEvent): CostState;
18
+ export interface LoopState {
19
+ step: number;
20
+ status: "idle" | "running" | "paused" | "error";
21
+ lastError: string | null;
22
+ }
23
+ export declare const initialLoopState: () => LoopState;
24
+ export declare function loopStateReducer(state: LoopState, event: HaloEvent): LoopState;
25
+ export interface PendingApproval {
26
+ toolCallId: string;
27
+ call: ToolCall;
28
+ }
29
+ export declare const initialApprovals: () => PendingApproval[];
30
+ export declare function approvalsReducer(state: PendingApproval[], event: HaloEvent): PendingApproval[];
31
+ //# sourceMappingURL=reducers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducers.d.ts","sourceRoot":"","sources":["../src/reducers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK1D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,iBAAiB,QAAO,eAKnC,CAAC;AAEH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,GAAG,eAAe,CAW3F;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,QAAO,SAK7B,CAAC;AAEH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAczE;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAChD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,eAAO,MAAM,gBAAgB,QAAO,SAA2D,CAAC;AAEhG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAe9E;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB,QAAO,eAAe,EAAQ,CAAC;AAE5D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,eAAe,EAAE,CAS9F"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@halo-sdk/react",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight, strongly-typed React hooks for Halo SDK — observe cache, cost, loop, and HITL state in the UI",
5
+ "keywords": [
6
+ "ai",
7
+ "hooks",
8
+ "llm",
9
+ "observability",
10
+ "prefix-cache",
11
+ "react"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/halo-sdk/halo-ai",
17
+ "directory": "packages/react"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^19.0.0",
37
+ "react": "^19.0.0",
38
+ "typescript": "^5.8.0",
39
+ "vitest": "^3.0.0",
40
+ "@halo-sdk/core": "1.1.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@halo-sdk/core": ">=1.1.0",
44
+ "react": "^18.0.0 || ^19.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc --build --emitDeclarationOnly && tsup",
48
+ "dev": "tsup --watch",
49
+ "clean": "del-cli dist *.tsbuildinfo",
50
+ "publint": "publint",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest"
53
+ }
54
+ }