@neeter/react
Advanced tools
+1
-1
@@ -12,3 +12,3 @@ import "./widgets/AskUserQuestionWidget.js"; | ||
| import "./widgets/WriteWidget.js"; | ||
| export type { ChatMessage, CustomEvent, PermissionRequest, PermissionResponse, SessionHistoryEntry, SessionInitEvent, SSEEvent, ToolApprovalRequest, ToolApprovalResponse, ToolCallInfo, ToolCallPhase, UserQuestion, UserQuestionOption, UserQuestionRequest, UserQuestionResponse, } from "@neeter/types"; | ||
| export type { ChatMessage, CustomEvent, ModelUsage, PermissionRequest, PermissionResponse, SessionHistoryEntry, SessionInitEvent, SSEEvent, TokenUsage, ToolApprovalRequest, ToolApprovalResponse, ToolCallInfo, ToolCallPhase, TurnCompleteData, UserQuestion, UserQuestionOption, UserQuestionRequest, UserQuestionResponse, } from "@neeter/types"; | ||
| export { AgentProvider, useAgentContext, useChatStore } from "./AgentProvider.js"; | ||
@@ -15,0 +15,0 @@ export { ChatInput } from "./ChatInput.js"; |
+5
-2
@@ -1,2 +0,2 @@ | ||
| import type { ChatMessage, McpServerStatus, PermissionRequest, SSEEvent } from "@neeter/types"; | ||
| import type { ChatMessage, McpServerStatus, ModelUsage, PermissionRequest, SSEEvent, TurnCompleteData } from "@neeter/types"; | ||
| import { type StoreApi } from "zustand/vanilla"; | ||
@@ -17,2 +17,5 @@ interface ChatStoreState { | ||
| totalTurns: number; | ||
| totalInputTokens: number; | ||
| totalOutputTokens: number; | ||
| modelUsage: Record<string, ModelUsage> | null; | ||
| } | ||
@@ -40,3 +43,3 @@ interface ChatStoreActions { | ||
| setFileCheckpointing: (v: boolean) => void; | ||
| addCost: (cost: number, turns: number) => void; | ||
| addCost: (data: TurnCompleteData) => void; | ||
| cancelInflightToolCalls: () => void; | ||
@@ -43,0 +46,0 @@ reset: () => void; |
+41
-4
@@ -34,2 +34,5 @@ import { immer } from "zustand/middleware/immer"; | ||
| totalTurns: 0, | ||
| totalInputTokens: 0, | ||
| totalOutputTokens: 0, | ||
| modelUsage: null, | ||
| setSessionId: (id) => set((s) => { | ||
@@ -154,5 +157,31 @@ s.sessionId = id; | ||
| }), | ||
| addCost: (cost, turns) => set((s) => { | ||
| s.totalCost += cost; | ||
| s.totalTurns += turns; | ||
| addCost: (data) => set((s) => { | ||
| s.totalCost += data.cost; | ||
| s.totalTurns += data.numTurns; | ||
| if (data.usage) { | ||
| s.totalInputTokens += data.usage.inputTokens; | ||
| s.totalOutputTokens += data.usage.outputTokens; | ||
| } | ||
| if (data.modelUsage) { | ||
| if (!s.modelUsage) { | ||
| s.modelUsage = data.modelUsage; | ||
| } | ||
| else { | ||
| for (const [model, usage] of Object.entries(data.modelUsage)) { | ||
| const existing = s.modelUsage[model]; | ||
| if (existing) { | ||
| existing.inputTokens += usage.inputTokens; | ||
| existing.outputTokens += usage.outputTokens; | ||
| existing.cacheCreationInputTokens += usage.cacheCreationInputTokens; | ||
| existing.cacheReadInputTokens += usage.cacheReadInputTokens; | ||
| existing.webSearchRequests += usage.webSearchRequests; | ||
| existing.costUSD += usage.costUSD; | ||
| existing.contextWindow = usage.contextWindow; | ||
| } | ||
| else { | ||
| s.modelUsage[model] = { ...usage }; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }), | ||
@@ -187,2 +216,5 @@ cancelInflightToolCalls: () => set((s) => { | ||
| s.totalTurns = 0; | ||
| s.totalInputTokens = 0; | ||
| s.totalOutputTokens = 0; | ||
| s.modelUsage = null; | ||
| }), | ||
@@ -259,3 +291,8 @@ }))); | ||
| s.flushStreamingText(); | ||
| s.addCost(data.cost ?? 0, data.numTurns ?? 0); | ||
| s.addCost({ | ||
| cost: data.cost ?? 0, | ||
| numTurns: data.numTurns ?? 0, | ||
| usage: data.usage ?? null, | ||
| modelUsage: data.modelUsage ?? null, | ||
| }); | ||
| break; | ||
@@ -262,0 +299,0 @@ case "session_error": |
@@ -125,3 +125,3 @@ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; | ||
| es.addEventListener("turn_complete", (e) => { | ||
| const { cost, numTurns } = JSON.parse(e.data); | ||
| const data = JSON.parse(e.data); | ||
| store.getState().flushStreamingThinking(); | ||
@@ -131,3 +131,8 @@ store.getState().flushStreamingText(); | ||
| store.getState().setStreaming(false); | ||
| store.getState().addCost(cost ?? 0, numTurns ?? 0); | ||
| store.getState().addCost({ | ||
| cost: data.cost ?? 0, | ||
| numTurns: data.numTurns ?? 0, | ||
| usage: data.usage ?? null, | ||
| modelUsage: data.modelUsage ?? null, | ||
| }); | ||
| if (abortedRef.current) { | ||
@@ -134,0 +139,0 @@ store.getState().cancelInflightToolCalls(); |
+2
-2
| { | ||
| "name": "@neeter/react", | ||
| "version": "0.13.1", | ||
| "version": "0.14.0", | ||
| "description": "React components and hooks for building chat UIs on top of the Claude Agent SDK", | ||
@@ -29,3 +29,3 @@ "license": "MIT", | ||
| "tailwind-merge": "^3.5.0", | ||
| "@neeter/types": "0.13.1" | ||
| "@neeter/types": "0.14.0" | ||
| }, | ||
@@ -32,0 +32,0 @@ "peerDependencies": { |
112549
1.8%2151
2.14%+ Added
- Removed
Updated