🚀 Socket Launch Week Day 4:Socket MCP Adds Org Alerts, Threat Feed Review, and Package Inspection.Learn more
Sign In

@agentick/react

Package Overview
Dependencies
Maintainers
2
Versions
97
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agentick/react - npm Package Compare versions

Comparing version
0.4.0
to
0.5.0
+12
dist/hooks/index.d.ts
export { useClient } from "./use-client";
export { useConnection, useConnectionState } from "./use-connection";
export { useSession } from "./use-session";
export { useEvents } from "./use-events";
export { useStreamingText } from "./use-streaming-text";
export { useContextInfo, type ContextInfo, type UseContextInfoOptions, type UseContextInfoResult, } from "./use-context-info";
export { useMessageSteering, type UseMessageSteeringOptions, type UseMessageSteeringResult, type MessageSteeringState, type SteeringMode, type FlushMode, } from "./use-message-steering";
export { useMessages, type UseMessagesOptions, type UseMessagesResult } from "./use-messages";
export { useToolConfirmations, type UseToolConfirmationsOptions, type UseToolConfirmationsResult, } from "./use-tool-confirmations";
export { useChat, type UseChatOptions, type UseChatResult, type ChatMode, type ChatMessage, type ToolConfirmationState, type Attachment, type AttachmentInput, } from "./use-chat";
export { useLineEditor, type UseLineEditorOptions, type LineEditorResult } from "./use-line-editor";
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9F,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
export { useClient } from "./use-client";
export { useConnection, useConnectionState } from "./use-connection";
export { useSession } from "./use-session";
export { useEvents } from "./use-events";
export { useStreamingText } from "./use-streaming-text";
export { useContextInfo, } from "./use-context-info";
export { useMessageSteering, } from "./use-message-steering";
export { useMessages } from "./use-messages";
export { useToolConfirmations, } from "./use-tool-confirmations";
export { useChat, } from "./use-chat";
export { useLineEditor } from "./use-line-editor";
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,cAAc,GAIf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,kBAAkB,GAMnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAmD,MAAM,gBAAgB,CAAC;AAC9F,OAAO,EACL,oBAAoB,GAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,GAQR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAoD,MAAM,mBAAmB,CAAC"}
import { type ChatSessionOptions, type ChatMode, type ChatMessage, type ToolConfirmationState, type SteeringMode, type Attachment, type AttachmentInput } from "@agentick/client";
import type { ToolConfirmationResponse, Message, ClientExecutionHandle } from "@agentick/client";
export type { ChatMode, ChatMessage, ToolConfirmationState, Attachment, AttachmentInput };
export type UseChatOptions<TMode extends string = ChatMode> = ChatSessionOptions<TMode>;
export interface UseChatResult<TMode extends string = ChatMode> {
messages: readonly ChatMessage[];
chatMode: TMode;
toolConfirmation: ToolConfirmationState | null;
lastSubmitted: string | null;
queued: readonly Message[];
isExecuting: boolean;
mode: SteeringMode;
/** Error from the most recent execution failure (null on success or abort) */
error: {
message: string;
name: string;
} | null;
attachments: readonly Attachment[];
submit: (text: string) => void;
steer: (text: string) => void;
queue: (text: string) => void;
interrupt: (text: string) => Promise<ClientExecutionHandle>;
abort: (reason?: string) => void;
flush: () => void;
removeQueued: (index: number) => void;
clearQueued: () => void;
setMode: (mode: SteeringMode) => void;
respondToConfirmation: (response: ToolConfirmationResponse) => void;
clearMessages: () => void;
addAttachment: (input: AttachmentInput) => Attachment;
removeAttachment: (id: string) => void;
clearAttachments: () => void;
}
/**
* Full chat controller hook — messages, steering, and tool confirmations.
*
* Wraps `ChatSession` with `useSyncExternalStore` for concurrent-safe React state.
*
* Options like `transform`, `confirmationPolicy`, and `deriveMode` are captured
* at mount time. Changing them requires a new `sessionId` to take effect.
*/
export declare function useChat<TMode extends string = ChatMode>(options?: UseChatOptions<TMode>): UseChatResult<TMode>;
//# sourceMappingURL=use-chat.d.ts.map
{"version":3,"file":"use-chat.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EAEvB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGjG,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,qBAAqB,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;AAC1F,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,GAAG,QAAQ,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAC;AAExF,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,GAAG,QAAQ;IAC5D,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC;IAChB,gBAAgB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC/C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,8EAA8E;IAC9E,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;IAEnC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5D,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACtC,qBAAqB,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACpE,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,UAAU,CAAC;IACtD,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAcD;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,SAAS,MAAM,GAAG,QAAQ,EACrD,OAAO,GAAE,cAAc,CAAC,KAAK,CAA+B,GAC3D,aAAa,CAAC,KAAK,CAAC,CAuCtB"}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import { ChatSession, } from "@agentick/client";
import { useClient } from "./use-client";
const INITIAL_STATE = {
messages: [],
chatMode: "idle",
toolConfirmation: null,
lastSubmitted: null,
queued: [],
isExecuting: false,
mode: "steer",
error: null,
attachments: [],
};
/**
* Full chat controller hook — messages, steering, and tool confirmations.
*
* Wraps `ChatSession` with `useSyncExternalStore` for concurrent-safe React state.
*
* Options like `transform`, `confirmationPolicy`, and `deriveMode` are captured
* at mount time. Changing them requires a new `sessionId` to take effect.
*/
export function useChat(options = {}) {
const client = useClient();
const session = useMemo(() => new ChatSession(client, options), [client, options.sessionId]);
useEffect(() => () => session.destroy(), [session]);
const state = useSyncExternalStore(useCallback((cb) => session.onStateChange(cb), [session]), () => session.state, () => INITIAL_STATE);
return {
...state,
submit: useCallback((t) => session.submit(t), [session]),
steer: useCallback((t) => session.steer(t), [session]),
queue: useCallback((t) => session.queue(t), [session]),
interrupt: useCallback((t) => session.interrupt(t), [session]),
abort: useCallback((r) => session.abort(r), [session]),
flush: useCallback(() => session.flush(), [session]),
removeQueued: useCallback((i) => session.removeQueued(i), [session]),
clearQueued: useCallback(() => session.clearQueued(), [session]),
setMode: useCallback((m) => session.setMode(m), [session]),
respondToConfirmation: useCallback((r) => session.respondToConfirmation(r), [session]),
clearMessages: useCallback(() => session.clearMessages(), [session]),
addAttachment: useCallback((input) => session.attachments.add(input), [session]),
removeAttachment: useCallback((id) => session.attachments.remove(id), [session]),
clearAttachments: useCallback(() => session.attachments.clear(), [session]),
};
}
//# sourceMappingURL=use-chat.js.map
{"version":3,"file":"use-chat.js","sourceRoot":"","sources":["../../src/hooks/use-chat.ts"],"names":[],"mappings":"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"}
import type { AgentickClient } from "@agentick/client";
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export declare function useClient(): AgentickClient;
//# sourceMappingURL=use-client.d.ts.map
{"version":3,"file":"use-client.d.ts","sourceRoot":"","sources":["../../src/hooks/use-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOvD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,IAAI,cAAc,CAQ1C"}
import { useContext } from "react";
import { AgentickContext } from "../context";
// ============================================================================
// useClient
// ============================================================================
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export function useClient() {
const context = useContext(AgentickContext);
if (!context) {
throw new Error("useClient must be used within a AgentickProvider");
}
return context.client;
}
//# sourceMappingURL=use-client.js.map
{"version":3,"file":"use-client.js","sourceRoot":"","sources":["../../src/hooks/use-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAE5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC"}
import type { ConnectionState } from "@agentick/client";
import type { UseConnectionOptions, UseConnectionResult } from "../types";
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export declare function useConnectionState(): ConnectionState;
/**
* Read the SSE connection state.
*/
export declare function useConnection(_options?: UseConnectionOptions): UseConnectionResult;
//# sourceMappingURL=use-connection.d.ts.map
{"version":3,"file":"use-connection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAM1E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAcpD;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,GAAE,oBAAyB,GAAG,mBAAmB,CActF"}
import { useState, useEffect } from "react";
import { useClient } from "./use-client";
// ============================================================================
// useConnectionState (alias for useConnection)
// ============================================================================
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export function useConnectionState() {
const client = useClient();
const [state, setState] = useState(client.state);
useEffect(() => {
// Sync initial state
setState(client.state);
// Subscribe to changes
const unsubscribe = client.onConnectionChange(setState);
return unsubscribe;
}, [client]);
return state;
}
// ============================================================================
// useConnection
// ============================================================================
/**
* Read the SSE connection state.
*/
export function useConnection(_options = {}) {
const client = useClient();
const [state, setState] = useState(client.state);
useEffect(() => {
setState(client.state);
return client.onConnectionChange(setState);
}, [client]);
return {
state,
isConnected: state === "connected",
isConnecting: state === "connecting",
};
}
//# sourceMappingURL=use-connection.js.map
{"version":3,"file":"use-connection.js","sourceRoot":"","sources":["../../src/hooks/use-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,+EAA+E;AAC/E,+CAA+C;AAC/C,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,KAAK,CAAC,CAAC;IAElE,SAAS,CAAC,GAAG,EAAE;QACb,qBAAqB;QACrB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEvB,uBAAuB;QACvB,MAAM,WAAW,GAAG,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACxD,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,WAAiC,EAAE;IAC/D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,KAAK,CAAC,CAAC;IAElE,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO;QACL,KAAK;QACL,WAAW,EAAE,KAAK,KAAK,WAAW;QAClC,YAAY,EAAE,KAAK,KAAK,YAAY;KACrC,CAAC;AACJ,CAAC"}
import type { ContextInfo } from "@agentick/shared";
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export { type ContextInfo };
/**
* Options for useContextInfo hook.
*/
export interface UseContextInfoOptions {
/**
* Optional session ID to filter events for.
* If not provided, receives context info from any session.
*/
sessionId?: string;
/**
* Whether the hook is enabled.
* If false, no context info subscription is created.
* @default true
*/
enabled?: boolean;
}
/**
* Return value from useContextInfo hook.
*/
export interface UseContextInfoResult {
/**
* Latest context info (null before first tick completes).
*/
contextInfo: ContextInfo | null;
/**
* Clear the current context info.
*/
clear: () => void;
}
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export declare function useContextInfo(options?: UseContextInfoOptions): UseContextInfoResult;
//# sourceMappingURL=use-context-info.d.ts.map
{"version":3,"file":"use-context-info.d.ts","sourceRoot":"","sources":["../../src/hooks/use-context-info.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAOpD;;;GAGG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAEhC;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,CA+DxF"}
import { useState, useEffect, useCallback } from "react";
import { useClient } from "./use-client";
// ============================================================================
// useContextInfo
// ============================================================================
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export {};
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export function useContextInfo(options = {}) {
const { sessionId, enabled = true } = options;
const client = useClient();
const [contextInfo, setContextInfo] = useState(null);
useEffect(() => {
if (!enabled)
return;
// Subscribe to context_update events
const handleEvent = (event) => {
if (event.type !== "context_update")
return;
// Type assertion since we filtered by type
const ctxEvent = event;
setContextInfo({
modelId: ctxEvent.modelId,
modelName: ctxEvent.modelName,
provider: ctxEvent.provider,
contextWindow: ctxEvent.contextWindow,
inputTokens: ctxEvent.inputTokens,
outputTokens: ctxEvent.outputTokens,
totalTokens: ctxEvent.totalTokens,
utilization: ctxEvent.utilization,
maxOutputTokens: ctxEvent.maxOutputTokens,
supportsVision: ctxEvent.supportsVision,
supportsToolUse: ctxEvent.supportsToolUse,
isReasoningModel: ctxEvent.isReasoningModel,
tick: ctxEvent.tick,
cumulativeUsage: ctxEvent.cumulativeUsage,
});
};
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
return accessor.onEvent(handleEvent);
}
// Global subscription
return client.onEvent(handleEvent);
}, [client, sessionId, enabled]);
const clear = useCallback(() => {
setContextInfo(null);
}, []);
return { contextInfo, clear };
}
//# sourceMappingURL=use-context-info.js.map
{"version":3,"file":"use-context-info.js","sourceRoot":"","sources":["../../src/hooks/use-context-info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAGzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;GAGG;AACH,OAAO,EAAoB,CAAC;AAmC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,UAAU,cAAc,CAAC,UAAiC,EAAE;IAChE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IAEzE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,qCAAqC;QACrC,MAAM,WAAW,GAAG,CAAC,KAAuC,EAAE,EAAE;YAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB;gBAAE,OAAO;YAE5C,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,KAehB,CAAC;YAEF,cAAc,CAAC;gBACb,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,aAAa,EAAE,QAAQ,CAAC,aAAa;gBACrC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,eAAe,EAAE,QAAQ,CAAC,eAAe;gBACzC,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,eAAe,EAAE,QAAQ,CAAC,eAAe;gBACzC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;gBAC3C,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,eAAe,EAAE,QAAQ,CAAC,eAAe;aAC1C,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,0DAA0D;QAC1D,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC3C,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACvC,CAAC;QAED,sBAAsB;QACtB,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAEjC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,cAAc,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC"}
import type { UseEventsOptions, UseEventsResult } from "../types";
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export declare function useEvents(options?: UseEventsOptions): UseEventsResult;
//# sourceMappingURL=use-events.d.ts.map
{"version":3,"file":"use-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAMlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAqCzE"}
import { useState, useEffect, useCallback } from "react";
import { useClient } from "./use-client";
// ============================================================================
// useEvents
// ============================================================================
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export function useEvents(options = {}) {
const { filter, sessionId, enabled = true } = options;
const client = useClient();
const [event, setEvent] = useState();
useEffect(() => {
if (!enabled)
return;
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
const unsubscribe = accessor.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}
// Global subscription
const unsubscribe = client.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}, [client, sessionId, enabled, filter]);
const clear = useCallback(() => {
setEvent(undefined);
}, []);
return { event, clear };
}
//# sourceMappingURL=use-events.js.map
{"version":3,"file":"use-events.js","sourceRoot":"","sources":["../../src/hooks/use-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAEtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAgD,CAAC;IAEnF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,0DAA0D;QAC1D,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAChD,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9C,OAAO;gBACT,CAAC;gBACD,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC;YACH,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,sBAAsB;QACtB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC9C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9C,OAAO;YACT,CAAC;YACD,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC"}
/**
* useLineEditor — React wrapper around @agentick/client's LineEditor.
*
* For web consumers. No key normalization included — the caller normalizes
* DOM KeyboardEvent to keystroke strings and calls editor.handleInput directly,
* or uses the returned LineEditor instance for full control.
*/
import { LineEditor, type LineEditorOptions, type CompletionState, type CompletedRange } from "@agentick/client";
export interface UseLineEditorOptions {
onSubmit: (value: string) => void;
bindings?: LineEditorOptions["bindings"];
}
export interface LineEditorResult {
value: string;
cursor: number;
completion: CompletionState | null;
completedRanges: readonly CompletedRange[];
setValue: (value: string) => void;
clear: () => void;
/** Process a normalized keystroke. See LineEditor.handleInput. */
handleInput: (keystroke: string | null, text: string) => void;
/** Direct access to the underlying LineEditor instance. */
editor: LineEditor;
}
export declare function useLineEditor(options: UseLineEditorOptions): LineEditorResult;
//# sourceMappingURL=use-line-editor.d.ts.map
{"version":3,"file":"use-line-editor.d.ts","sourceRoot":"","sources":["../../src/hooks/use-line-editor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,UAAU,EAEV,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,kEAAkE;IAClE,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,2DAA2D;IAC3D,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAkC7E"}
/**
* useLineEditor — React wrapper around @agentick/client's LineEditor.
*
* For web consumers. No key normalization included — the caller normalizes
* DOM KeyboardEvent to keystroke strings and calls editor.handleInput directly,
* or uses the returned LineEditor instance for full control.
*/
import { useMemo, useEffect, useCallback, useRef, useSyncExternalStore } from "react";
import { LineEditor, EMPTY_SNAPSHOT, } from "@agentick/client";
export function useLineEditor(options) {
const onSubmitRef = useRef(options.onSubmit);
onSubmitRef.current = options.onSubmit;
const editor = useMemo(() => new LineEditor({
onSubmit: (v) => onSubmitRef.current(v),
bindings: options.bindings,
}), []);
useEffect(() => () => editor.destroy(), [editor]);
const state = useSyncExternalStore(useCallback((cb) => editor.onStateChange(cb), [editor]), () => editor.state, () => EMPTY_SNAPSHOT);
return {
value: state.value,
cursor: state.cursor,
completion: state.completion,
completedRanges: state.completedRanges,
setValue: useCallback((v) => editor.setValue(v), [editor]),
clear: useCallback(() => editor.clear(), [editor]),
handleInput: useCallback((keystroke, text) => editor.handleInput(keystroke, text), [editor]),
editor,
};
}
//# sourceMappingURL=use-line-editor.js.map
{"version":3,"file":"use-line-editor.js","sourceRoot":"","sources":["../../src/hooks/use-line-editor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EACL,UAAU,EACV,cAAc,GAIf,MAAM,kBAAkB,CAAC;AAoB1B,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEvC,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CACH,IAAI,UAAU,CAAC;QACb,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,EACJ,EAAE,CACH,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAElD,MAAM,KAAK,GAAG,oBAAoB,CAChC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EACvD,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAClB,GAAG,EAAE,CAAC,cAAc,CACrB,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAClE,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAClD,WAAW,EAAE,WAAW,CACtB,CAAC,SAAwB,EAAE,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,EAC/E,CAAC,MAAM,CAAC,CACT;QACD,MAAM;KACP,CAAC;AACJ,CAAC"}
import { type MessageSteeringOptions, type SteeringMode, type FlushMode, type MessageSteeringState } from "@agentick/client";
import type { ClientExecutionHandle, Message } from "@agentick/client";
export type { SteeringMode, FlushMode, MessageSteeringState };
export type UseMessageSteeringOptions = MessageSteeringOptions;
export interface UseMessageSteeringResult {
/** Mode-aware send. If idle, sends immediately. If executing: "steer" mode sends concurrently, "queue" mode queues for later. */
submit: (text: string) => void;
/** Always sends immediately regardless of mode or execution state. */
steer: (text: string) => void;
/** Always queues regardless of mode or execution state. */
queue: (text: string) => void;
/** Abort the current execution and immediately send a new message. */
interrupt: (text: string) => Promise<ClientExecutionHandle>;
/** Messages waiting to be sent. */
queued: readonly Message[];
/** Flush queued messages. In "sequential" flushMode, sends one; in "batched", sends all. */
flush: () => void;
/** Remove a queued message by index. */
removeQueued: (index: number) => void;
/** Clear all queued messages without sending. */
clearQueued: () => void;
/** Current steering mode ("steer" | "queue"). Default: "steer". */
mode: SteeringMode;
/** Change the steering mode at runtime. */
setMode: (mode: SteeringMode) => void;
/** Whether an execution is currently in-flight. */
isExecuting: boolean;
}
export declare function useMessageSteering(options?: UseMessageSteeringOptions): UseMessageSteeringResult;
//# sourceMappingURL=use-message-steering.d.ts.map
{"version":3,"file":"use-message-steering.d.ts","sourceRoot":"","sources":["../../src/hooks/use-message-steering.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGvE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC;AAC9D,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,iIAAiI;IACjI,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sEAAsE;IACtE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,sEAAsE;IACtE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC5D,mCAAmC;IACnC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAC3B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,wCAAwC;IACxC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iDAAiD;IACjD,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,mEAAmE;IACnE,IAAI,EAAE,YAAY,CAAC;IACnB,2CAA2C;IAC3C,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACtC,mDAAmD;IACnD,WAAW,EAAE,OAAO,CAAC;CACtB;AA+DD,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CA0B1B"}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import { MessageSteering, } from "@agentick/client";
import { useClient } from "./use-client";
/**
* Manage message submission with queuing, interruption, and steering.
*
* Wraps the client's `MessageSteering` controller as a React hook with
* concurrent-safe state via `useSyncExternalStore`.
*
* Two steering modes control what `submit` does during an active execution:
* - **"steer"** (default) — sends concurrently alongside the running execution.
* - **"queue"** — buffers messages until the execution completes, then
* auto-flushes (one-at-a-time in "sequential" flushMode, all-at-once in "batched").
*
* `steer()`, `queue()`, and `interrupt()` bypass the mode and always do
* exactly what their name says.
*
* @example Queue mode with sequential flush
* ```tsx
* import { useMessageSteering } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { submit, queued, isExecuting } = useMessageSteering({
* sessionId,
* mode: 'queue',
* flushMode: 'sequential',
* });
*
* return (
* <div>
* <button onClick={() => submit('Hello!')}>Send</button>
* {isExecuting && <span>Thinking...</span>}
* {queued.length > 0 && <span>{queued.length} queued</span>}
* </div>
* );
* }
* ```
*
* @example Using interrupt to cancel and redirect
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { submit, interrupt, isExecuting } = useMessageSteering({
* sessionId,
* });
*
* const handleSend = (text: string) => {
* if (isExecuting) {
* interrupt(text); // Aborts current execution, sends new message
* } else {
* submit(text);
* }
* };
*
* return <ChatInput onSubmit={handleSend} />;
* }
* ```
*/
const INITIAL_STATE = {
queued: [],
isExecuting: false,
mode: "steer",
};
export function useMessageSteering(options = {}) {
const client = useClient();
const steering = useMemo(() => new MessageSteering(client, options), [client, options.sessionId]);
useEffect(() => () => steering.destroy(), [steering]);
const state = useSyncExternalStore(useCallback((cb) => steering.onStateChange(cb), [steering]), () => steering.state, () => INITIAL_STATE);
return {
submit: useCallback((text) => steering.submit(text), [steering]),
steer: useCallback((text) => steering.steer(text), [steering]),
queue: useCallback((text) => steering.queue(text), [steering]),
interrupt: useCallback((text) => steering.interrupt(text), [steering]),
flush: useCallback(() => steering.flush(), [steering]),
removeQueued: useCallback((i) => steering.removeQueued(i), [steering]),
clearQueued: useCallback(() => steering.clearQueued(), [steering]),
setMode: useCallback((m) => steering.setMode(m), [steering]),
queued: state.queued,
isExecuting: state.isExecuting,
mode: state.mode,
};
}
//# sourceMappingURL=use-message-steering.js.map
{"version":3,"file":"use-message-steering.js","sourceRoot":"","sources":["../../src/hooks/use-message-steering.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9E,OAAO,EACL,eAAe,GAKhB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AA8BzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,MAAM,aAAa,GAAyB;IAC1C,MAAM,EAAE,EAAE;IACV,WAAW,EAAE,KAAK;IAClB,IAAI,EAAE,OAAO;CACd,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,UAAqC,EAAE;IAEvC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAElG,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,oBAAoB,CAChC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAC3D,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EACpB,GAAG,EAAE,CAAC,aAAa,CACpB,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACxE,KAAK,EAAE,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtE,KAAK,EAAE,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtE,SAAS,EAAE,WAAW,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC9E,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtD,YAAY,EAAE,WAAW,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC9E,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;QAClE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAC1E,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;AACJ,CAAC"}
import { type MessageLogOptions, type ChatMessage } from "@agentick/client";
export type UseMessagesOptions = MessageLogOptions;
export interface UseMessagesResult {
messages: readonly ChatMessage[];
clear: () => void;
}
/**
* Message accumulation hook — wraps `MessageLog` with `useSyncExternalStore`.
*
* Use standalone for message-only UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export declare function useMessages(options?: UseMessagesOptions): UseMessagesResult;
//# sourceMappingURL=use-messages.d.ts.map
{"version":3,"file":"use-messages.d.ts","sourceRoot":"","sources":["../../src/hooks/use-messages.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CAiB/E"}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import { MessageLog, } from "@agentick/client";
import { useClient } from "./use-client";
const INITIAL_STATE = {
messages: [],
};
/**
* Message accumulation hook — wraps `MessageLog` with `useSyncExternalStore`.
*
* Use standalone for message-only UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export function useMessages(options = {}) {
const client = useClient();
const log = useMemo(() => new MessageLog(client, options), [client, options.sessionId]);
useEffect(() => () => log.destroy(), [log]);
const state = useSyncExternalStore(useCallback((cb) => log.onStateChange(cb), [log]), () => log.state, () => INITIAL_STATE);
return {
messages: state.messages,
clear: useCallback(() => log.clear(), [log]),
};
}
//# sourceMappingURL=use-messages.js.map
{"version":3,"file":"use-messages.js","sourceRoot":"","sources":["../../src/hooks/use-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9E,OAAO,EACL,UAAU,GAIX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AASzC,MAAM,aAAa,GAAoB;IACrC,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE;IAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAExF,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5C,MAAM,KAAK,GAAG,oBAAoB,CAChC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EACjD,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,EACf,GAAG,EAAE,CAAC,aAAa,CACpB,CAAC;IAEF,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;KAC7C,CAAC;AACJ,CAAC"}
import type { UseSessionOptions, UseSessionResult } from "../types";
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export declare function useSession(options?: UseSessionOptions): UseSessionResult;
//# sourceMappingURL=use-session.d.ts.map
{"version":3,"file":"use-session.d.ts","sourceRoot":"","sources":["../../src/hooks/use-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAMpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAoG5E"}
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { useClient } from "./use-client";
// ============================================================================
// useSession
// ============================================================================
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export function useSession(options = {}) {
const { sessionId, autoSubscribe = false } = options;
const client = useClient();
const mountedRef = useRef(true);
// Get or create session accessor
const accessor = useMemo(() => {
if (!sessionId)
return undefined;
return client.session(sessionId);
}, [client, sessionId]);
const [isSubscribed, setIsSubscribed] = useState(false);
// Cleanup on unmount
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
// Subscribe function
const subscribe = useCallback(() => {
if (!accessor)
return;
accessor.subscribe();
if (mountedRef.current) {
setIsSubscribed(true);
}
}, [accessor]);
// Unsubscribe function
const unsubscribe = useCallback(() => {
if (!accessor)
return;
accessor.unsubscribe();
if (mountedRef.current) {
setIsSubscribed(false);
}
}, [accessor]);
// Auto-subscribe
useEffect(() => {
if (autoSubscribe && accessor && !isSubscribed) {
subscribe();
}
}, [autoSubscribe, accessor, isSubscribed, subscribe]);
// Send function
const send = useCallback((input) => {
if (accessor) {
const normalizedInput = typeof input === "string"
? {
messages: [
{
role: "user",
content: [{ type: "text", text: input }],
},
],
}
: input;
return accessor.send(normalizedInput);
}
return client.send(input);
}, [client, accessor]);
// Abort function
const abort = useCallback(async (reason) => {
if (accessor) {
await accessor.abort(reason);
}
else if (sessionId) {
await client.abort(sessionId, reason);
}
}, [client, accessor, sessionId]);
// Close function
const close = useCallback(async () => {
if (accessor) {
await accessor.close();
}
else if (sessionId) {
await client.closeSession(sessionId);
}
}, [client, accessor, sessionId]);
return {
sessionId,
isSubscribed,
subscribe,
unsubscribe,
send,
abort,
close,
accessor,
};
}
//# sourceMappingURL=use-session.js.map
{"version":3,"file":"use-session.js","sourceRoot":"","sources":["../../src/hooks/use-session.ts"],"names":[],"mappings":"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"}
import type { UseStreamingTextOptions, UseStreamingTextResult } from "../types";
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export declare function useStreamingText(options?: UseStreamingTextOptions): UseStreamingTextResult;
//# sourceMappingURL=use-streaming-text.d.ts.map
{"version":3,"file":"use-streaming-text.d.ts","sourceRoot":"","sources":["../../src/hooks/use-streaming-text.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAMhF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,sBAAsB,CAsB9F"}
import { useSyncExternalStore, useCallback } from "react";
import { useClient } from "./use-client";
// ============================================================================
// useStreamingText
// ============================================================================
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export function useStreamingText(options = {}) {
const { enabled = true } = options;
const client = useClient();
// Use useSyncExternalStore for concurrent-safe subscription
const state = useSyncExternalStore(useCallback((onStoreChange) => {
if (!enabled)
return () => { };
return client.onStreamingText(onStoreChange);
}, [client, enabled]), () => (enabled ? client.streamingText : { text: "", isStreaming: false }), () => ({ text: "", isStreaming: false }));
const clear = useCallback(() => {
client.clearStreamingText();
}, [client]);
return { text: state.text, isStreaming: state.isStreaming, clear };
}
//# sourceMappingURL=use-streaming-text.js.map
{"version":3,"file":"use-streaming-text.js","sourceRoot":"","sources":["../../src/hooks/use-streaming-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACnC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,4DAA4D;IAC5D,MAAM,KAAK,GAAG,oBAAoB,CAChC,WAAW,CACT,CAAC,aAAa,EAAE,EAAE;QAChB,IAAI,CAAC,OAAO;YAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAC/C,CAAC,EACD,CAAC,MAAM,EAAE,OAAO,CAAC,CAClB,EACD,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EACzE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CACzC,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,CAAC,kBAAkB,EAAE,CAAC;IAC9B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,CAAC;AACrE,CAAC"}
import { type ToolConfirmationsOptions, type ToolConfirmationState } from "@agentick/client";
import type { ToolConfirmationResponse } from "@agentick/client";
export type UseToolConfirmationsOptions = ToolConfirmationsOptions;
export interface UseToolConfirmationsResult {
pending: ToolConfirmationState | null;
respond: (response: ToolConfirmationResponse) => void;
}
/**
* Tool confirmation hook — wraps `ToolConfirmations` with `useSyncExternalStore`.
*
* Use standalone for custom confirmation UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export declare function useToolConfirmations(options?: UseToolConfirmationsOptions): UseToolConfirmationsResult;
//# sourceMappingURL=use-tool-confirmations.d.ts.map
{"version":3,"file":"use-tool-confirmations.d.ts","sourceRoot":"","sources":["../../src/hooks/use-tool-confirmations.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,wBAAwB,EAE7B,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AAEnE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACvD;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,0BAA0B,CAiB5B"}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import { ToolConfirmations, } from "@agentick/client";
import { useClient } from "./use-client";
const INITIAL_STATE = {
pending: null,
};
/**
* Tool confirmation hook — wraps `ToolConfirmations` with `useSyncExternalStore`.
*
* Use standalone for custom confirmation UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export function useToolConfirmations(options = {}) {
const client = useClient();
const tc = useMemo(() => new ToolConfirmations(client, options), [client, options.sessionId]);
useEffect(() => () => tc.destroy(), [tc]);
const state = useSyncExternalStore(useCallback((cb) => tc.onStateChange(cb), [tc]), () => tc.state, () => INITIAL_STATE);
return {
pending: state.pending,
respond: useCallback((r) => tc.respond(r), [tc]),
};
}
//# sourceMappingURL=use-tool-confirmations.js.map
{"version":3,"file":"use-tool-confirmations.js","sourceRoot":"","sources":["../../src/hooks/use-tool-confirmations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9E,OAAO,EACL,iBAAiB,GAIlB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AASzC,MAAM,aAAa,GAA2B;IAC5C,OAAO,EAAE,IAAI;CACd,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAuC,EAAE;IAEzC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAE9F,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1C,MAAM,KAAK,GAAG,oBAAoB,CAChC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC/C,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EACd,GAAG,EAAE,CAAC,aAAa,CACpB,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,WAAW,CAAC,CAAC,CAA2B,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;KAC3E,CAAC;AACJ,CAAC"}
/**
* useMessageSteering Integration Tests
*
* Logic tests live in @agentick/client (MessageSteering class).
* These verify React integration: rendering, subscriptions, cleanup.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { type ReactNode } from "react";
import { AgentickProvider } from "../context";
import { useMessageSteering } from "../hooks/use-message-steering";
import type { AgentickClient, ConnectionState, StreamingTextState } from "@agentick/client";
import { createMockClient as createBaseMockClient, makeEvent } from "@agentick/client/testing";
// ============================================================================
// Mock Client (extends base with React-required fields)
// ============================================================================
function createMockClient() {
const base = createBaseMockClient();
return Object.assign(base, {
get state(): ConnectionState {
return "disconnected";
},
get streamingText(): StreamingTextState {
return { text: "", isStreaming: false };
},
closeSession: vi.fn(async () => {}),
subscribe: vi.fn((id: string) => base.getAccessor(id)),
onEvent: vi.fn(() => () => {}),
onConnectionChange: vi.fn(() => () => {}),
onStreamingText: vi.fn((handler: (state: StreamingTextState) => void) => {
handler({ text: "", isStreaming: false });
return () => {};
}),
clearStreamingText: vi.fn(),
on: vi.fn(() => () => {}),
}) as any as AgentickClient & {
_emitSessionEvent: ReturnType<typeof createBaseMockClient>["_emitSessionEvent"];
getAccessor: ReturnType<typeof createBaseMockClient>["getAccessor"];
};
}
function createWrapper(client: AgentickClient) {
return function Wrapper({ children }: { children: ReactNode }) {
return <AgentickProvider client={client}>{children}</AgentickProvider>;
};
}
// ============================================================================
// Tests
// ============================================================================
describe("useMessageSteering (React integration)", () => {
let client: ReturnType<typeof createMockClient>;
let wrapper: ReturnType<typeof createWrapper>;
beforeEach(() => {
client = createMockClient();
wrapper = createWrapper(client);
});
it("returns correct initial state", () => {
const { result } = renderHook(() => useMessageSteering({ sessionId: "s1" }), { wrapper });
expect(result.current.mode).toBe("steer");
expect(result.current.queued).toEqual([]);
expect(result.current.isExecuting).toBe(false);
});
it("re-renders on state changes", () => {
const { result } = renderHook(() => useMessageSteering({ sessionId: "s1" }), { wrapper });
act(() => result.current.queue("test"));
expect(result.current.queued).toHaveLength(1);
});
it("tracks execution state from events", () => {
const { result } = renderHook(() => useMessageSteering({ sessionId: "s1" }), { wrapper });
act(() => client._emitSessionEvent("s1", makeEvent("execution_start")));
expect(result.current.isExecuting).toBe(true);
act(() => client._emitSessionEvent("s1", makeEvent("execution_end")));
expect(result.current.isExecuting).toBe(false);
});
it("submit sends to client", () => {
const { result } = renderHook(() => useMessageSteering({ sessionId: "s1" }), { wrapper });
act(() => result.current.submit("Hello"));
expect(client.send).toHaveBeenCalledWith(
{ messages: [{ role: "user", content: [{ type: "text", text: "Hello" }] }] },
{ sessionId: "s1" },
);
});
it("mode switching works through React", () => {
const { result } = renderHook(() => useMessageSteering({ sessionId: "s1" }), { wrapper });
act(() => result.current.setMode("queue"));
expect(result.current.mode).toBe("queue");
});
});
export { useClient } from "./use-client";
export { useConnection, useConnectionState } from "./use-connection";
export { useSession } from "./use-session";
export { useEvents } from "./use-events";
export { useStreamingText } from "./use-streaming-text";
export {
useContextInfo,
type ContextInfo,
type UseContextInfoOptions,
type UseContextInfoResult,
} from "./use-context-info";
export {
useMessageSteering,
type UseMessageSteeringOptions,
type UseMessageSteeringResult,
type MessageSteeringState,
type SteeringMode,
type FlushMode,
} from "./use-message-steering";
export { useMessages, type UseMessagesOptions, type UseMessagesResult } from "./use-messages";
export {
useToolConfirmations,
type UseToolConfirmationsOptions,
type UseToolConfirmationsResult,
} from "./use-tool-confirmations";
export {
useChat,
type UseChatOptions,
type UseChatResult,
type ChatMode,
type ChatMessage,
type ToolConfirmationState,
type Attachment,
type AttachmentInput,
} from "./use-chat";
export { useLineEditor, type UseLineEditorOptions, type LineEditorResult } from "./use-line-editor";
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import {
ChatSession,
type ChatSessionOptions,
type ChatSessionState,
type ChatMode,
type ChatMessage,
type ToolConfirmationState,
type SteeringMode,
type Attachment,
type AttachmentInput,
} from "@agentick/client";
import type { ToolConfirmationResponse, Message, ClientExecutionHandle } from "@agentick/client";
import { useClient } from "./use-client";
export type { ChatMode, ChatMessage, ToolConfirmationState, Attachment, AttachmentInput };
export type UseChatOptions<TMode extends string = ChatMode> = ChatSessionOptions<TMode>;
export interface UseChatResult<TMode extends string = ChatMode> {
messages: readonly ChatMessage[];
chatMode: TMode;
toolConfirmation: ToolConfirmationState | null;
lastSubmitted: string | null;
queued: readonly Message[];
isExecuting: boolean;
mode: SteeringMode;
/** Error from the most recent execution failure (null on success or abort) */
error: { message: string; name: string } | null;
attachments: readonly Attachment[];
submit: (text: string) => void;
steer: (text: string) => void;
queue: (text: string) => void;
interrupt: (text: string) => Promise<ClientExecutionHandle>;
abort: (reason?: string) => void;
flush: () => void;
removeQueued: (index: number) => void;
clearQueued: () => void;
setMode: (mode: SteeringMode) => void;
respondToConfirmation: (response: ToolConfirmationResponse) => void;
clearMessages: () => void;
addAttachment: (input: AttachmentInput) => Attachment;
removeAttachment: (id: string) => void;
clearAttachments: () => void;
}
const INITIAL_STATE: ChatSessionState = {
messages: [],
chatMode: "idle",
toolConfirmation: null,
lastSubmitted: null,
queued: [],
isExecuting: false,
mode: "steer",
error: null,
attachments: [],
};
/**
* Full chat controller hook — messages, steering, and tool confirmations.
*
* Wraps `ChatSession` with `useSyncExternalStore` for concurrent-safe React state.
*
* Options like `transform`, `confirmationPolicy`, and `deriveMode` are captured
* at mount time. Changing them requires a new `sessionId` to take effect.
*/
export function useChat<TMode extends string = ChatMode>(
options: UseChatOptions<TMode> = {} as UseChatOptions<TMode>,
): UseChatResult<TMode> {
const client = useClient();
const session = useMemo(
() => new ChatSession<TMode>(client, options),
[client, options.sessionId],
);
useEffect(() => () => session.destroy(), [session]);
const state = useSyncExternalStore(
useCallback((cb) => session.onStateChange(cb), [session]),
() => session.state,
() => INITIAL_STATE as ChatSessionState<TMode>,
);
return {
...state,
submit: useCallback((t: string) => session.submit(t), [session]),
steer: useCallback((t: string) => session.steer(t), [session]),
queue: useCallback((t: string) => session.queue(t), [session]),
interrupt: useCallback((t: string) => session.interrupt(t), [session]),
abort: useCallback((r?: string) => session.abort(r), [session]),
flush: useCallback(() => session.flush(), [session]),
removeQueued: useCallback((i: number) => session.removeQueued(i), [session]),
clearQueued: useCallback(() => session.clearQueued(), [session]),
setMode: useCallback((m: SteeringMode) => session.setMode(m), [session]),
respondToConfirmation: useCallback(
(r: ToolConfirmationResponse) => session.respondToConfirmation(r),
[session],
),
clearMessages: useCallback(() => session.clearMessages(), [session]),
addAttachment: useCallback(
(input: AttachmentInput) => session.attachments.add(input),
[session],
),
removeAttachment: useCallback((id: string) => session.attachments.remove(id), [session]),
clearAttachments: useCallback(() => session.attachments.clear(), [session]),
};
}
import { useContext } from "react";
import type { AgentickClient } from "@agentick/client";
import { AgentickContext } from "../context";
// ============================================================================
// useClient
// ============================================================================
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export function useClient(): AgentickClient {
const context = useContext(AgentickContext);
if (!context) {
throw new Error("useClient must be used within a AgentickProvider");
}
return context.client;
}
import { useState, useEffect } from "react";
import type { ConnectionState } from "@agentick/client";
import { useClient } from "./use-client";
import type { UseConnectionOptions, UseConnectionResult } from "../types";
// ============================================================================
// useConnectionState (alias for useConnection)
// ============================================================================
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export function useConnectionState(): ConnectionState {
const client = useClient();
const [state, setState] = useState<ConnectionState>(client.state);
useEffect(() => {
// Sync initial state
setState(client.state);
// Subscribe to changes
const unsubscribe = client.onConnectionChange(setState);
return unsubscribe;
}, [client]);
return state;
}
// ============================================================================
// useConnection
// ============================================================================
/**
* Read the SSE connection state.
*/
export function useConnection(_options: UseConnectionOptions = {}): UseConnectionResult {
const client = useClient();
const [state, setState] = useState<ConnectionState>(client.state);
useEffect(() => {
setState(client.state);
return client.onConnectionChange(setState);
}, [client]);
return {
state,
isConnected: state === "connected",
isConnecting: state === "connecting",
};
}
import { useState, useEffect, useCallback } from "react";
import type { StreamEvent, SessionStreamEvent } from "@agentick/client";
import type { ContextInfo } from "@agentick/shared";
import { useClient } from "./use-client";
// ============================================================================
// useContextInfo
// ============================================================================
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export { type ContextInfo };
/**
* Options for useContextInfo hook.
*/
export interface UseContextInfoOptions {
/**
* Optional session ID to filter events for.
* If not provided, receives context info from any session.
*/
sessionId?: string;
/**
* Whether the hook is enabled.
* If false, no context info subscription is created.
* @default true
*/
enabled?: boolean;
}
/**
* Return value from useContextInfo hook.
*/
export interface UseContextInfoResult {
/**
* Latest context info (null before first tick completes).
*/
contextInfo: ContextInfo | null;
/**
* Clear the current context info.
*/
clear: () => void;
}
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export function useContextInfo(options: UseContextInfoOptions = {}): UseContextInfoResult {
const { sessionId, enabled = true } = options;
const client = useClient();
const [contextInfo, setContextInfo] = useState<ContextInfo | null>(null);
useEffect(() => {
if (!enabled) return;
// Subscribe to context_update events
const handleEvent = (event: StreamEvent | SessionStreamEvent) => {
if (event.type !== "context_update") return;
// Type assertion since we filtered by type
const ctxEvent = event as StreamEvent & {
modelId: string;
modelName?: string;
provider?: string;
contextWindow?: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
utilization?: number;
maxOutputTokens?: number;
supportsVision?: boolean;
supportsToolUse?: boolean;
isReasoningModel?: boolean;
tick: number;
cumulativeUsage?: ContextInfo["cumulativeUsage"];
};
setContextInfo({
modelId: ctxEvent.modelId,
modelName: ctxEvent.modelName,
provider: ctxEvent.provider,
contextWindow: ctxEvent.contextWindow,
inputTokens: ctxEvent.inputTokens,
outputTokens: ctxEvent.outputTokens,
totalTokens: ctxEvent.totalTokens,
utilization: ctxEvent.utilization,
maxOutputTokens: ctxEvent.maxOutputTokens,
supportsVision: ctxEvent.supportsVision,
supportsToolUse: ctxEvent.supportsToolUse,
isReasoningModel: ctxEvent.isReasoningModel,
tick: ctxEvent.tick,
cumulativeUsage: ctxEvent.cumulativeUsage,
});
};
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
return accessor.onEvent(handleEvent);
}
// Global subscription
return client.onEvent(handleEvent);
}, [client, sessionId, enabled]);
const clear = useCallback(() => {
setContextInfo(null);
}, []);
return { contextInfo, clear };
}
import { useState, useEffect, useCallback } from "react";
import type { StreamEvent, SessionStreamEvent } from "@agentick/client";
import { useClient } from "./use-client";
import type { UseEventsOptions, UseEventsResult } from "../types";
// ============================================================================
// useEvents
// ============================================================================
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export function useEvents(options: UseEventsOptions = {}): UseEventsResult {
const { filter, sessionId, enabled = true } = options;
const client = useClient();
const [event, setEvent] = useState<StreamEvent | SessionStreamEvent | undefined>();
useEffect(() => {
if (!enabled) return;
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
const unsubscribe = accessor.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}
// Global subscription
const unsubscribe = client.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}, [client, sessionId, enabled, filter]);
const clear = useCallback(() => {
setEvent(undefined);
}, []);
return { event, clear };
}
/**
* useLineEditor — React wrapper around @agentick/client's LineEditor.
*
* For web consumers. No key normalization included — the caller normalizes
* DOM KeyboardEvent to keystroke strings and calls editor.handleInput directly,
* or uses the returned LineEditor instance for full control.
*/
import { useMemo, useEffect, useCallback, useRef, useSyncExternalStore } from "react";
import {
LineEditor,
EMPTY_SNAPSHOT,
type LineEditorOptions,
type CompletionState,
type CompletedRange,
} from "@agentick/client";
export interface UseLineEditorOptions {
onSubmit: (value: string) => void;
bindings?: LineEditorOptions["bindings"];
}
export interface LineEditorResult {
value: string;
cursor: number;
completion: CompletionState | null;
completedRanges: readonly CompletedRange[];
setValue: (value: string) => void;
clear: () => void;
/** Process a normalized keystroke. See LineEditor.handleInput. */
handleInput: (keystroke: string | null, text: string) => void;
/** Direct access to the underlying LineEditor instance. */
editor: LineEditor;
}
export function useLineEditor(options: UseLineEditorOptions): LineEditorResult {
const onSubmitRef = useRef(options.onSubmit);
onSubmitRef.current = options.onSubmit;
const editor = useMemo(
() =>
new LineEditor({
onSubmit: (v) => onSubmitRef.current(v),
bindings: options.bindings,
}),
[],
);
useEffect(() => () => editor.destroy(), [editor]);
const state = useSyncExternalStore(
useCallback((cb) => editor.onStateChange(cb), [editor]),
() => editor.state,
() => EMPTY_SNAPSHOT,
);
return {
value: state.value,
cursor: state.cursor,
completion: state.completion,
completedRanges: state.completedRanges,
setValue: useCallback((v: string) => editor.setValue(v), [editor]),
clear: useCallback(() => editor.clear(), [editor]),
handleInput: useCallback(
(keystroke: string | null, text: string) => editor.handleInput(keystroke, text),
[editor],
),
editor,
};
}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import {
MessageSteering,
type MessageSteeringOptions,
type SteeringMode,
type FlushMode,
type MessageSteeringState,
} from "@agentick/client";
import type { ClientExecutionHandle, Message } from "@agentick/client";
import { useClient } from "./use-client";
export type { SteeringMode, FlushMode, MessageSteeringState };
export type UseMessageSteeringOptions = MessageSteeringOptions;
export interface UseMessageSteeringResult {
/** Mode-aware send. If idle, sends immediately. If executing: "steer" mode sends concurrently, "queue" mode queues for later. */
submit: (text: string) => void;
/** Always sends immediately regardless of mode or execution state. */
steer: (text: string) => void;
/** Always queues regardless of mode or execution state. */
queue: (text: string) => void;
/** Abort the current execution and immediately send a new message. */
interrupt: (text: string) => Promise<ClientExecutionHandle>;
/** Messages waiting to be sent. */
queued: readonly Message[];
/** Flush queued messages. In "sequential" flushMode, sends one; in "batched", sends all. */
flush: () => void;
/** Remove a queued message by index. */
removeQueued: (index: number) => void;
/** Clear all queued messages without sending. */
clearQueued: () => void;
/** Current steering mode ("steer" | "queue"). Default: "steer". */
mode: SteeringMode;
/** Change the steering mode at runtime. */
setMode: (mode: SteeringMode) => void;
/** Whether an execution is currently in-flight. */
isExecuting: boolean;
}
/**
* Manage message submission with queuing, interruption, and steering.
*
* Wraps the client's `MessageSteering` controller as a React hook with
* concurrent-safe state via `useSyncExternalStore`.
*
* Two steering modes control what `submit` does during an active execution:
* - **"steer"** (default) — sends concurrently alongside the running execution.
* - **"queue"** — buffers messages until the execution completes, then
* auto-flushes (one-at-a-time in "sequential" flushMode, all-at-once in "batched").
*
* `steer()`, `queue()`, and `interrupt()` bypass the mode and always do
* exactly what their name says.
*
* @example Queue mode with sequential flush
* ```tsx
* import { useMessageSteering } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { submit, queued, isExecuting } = useMessageSteering({
* sessionId,
* mode: 'queue',
* flushMode: 'sequential',
* });
*
* return (
* <div>
* <button onClick={() => submit('Hello!')}>Send</button>
* {isExecuting && <span>Thinking...</span>}
* {queued.length > 0 && <span>{queued.length} queued</span>}
* </div>
* );
* }
* ```
*
* @example Using interrupt to cancel and redirect
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { submit, interrupt, isExecuting } = useMessageSteering({
* sessionId,
* });
*
* const handleSend = (text: string) => {
* if (isExecuting) {
* interrupt(text); // Aborts current execution, sends new message
* } else {
* submit(text);
* }
* };
*
* return <ChatInput onSubmit={handleSend} />;
* }
* ```
*/
const INITIAL_STATE: MessageSteeringState = {
queued: [],
isExecuting: false,
mode: "steer",
};
export function useMessageSteering(
options: UseMessageSteeringOptions = {},
): UseMessageSteeringResult {
const client = useClient();
const steering = useMemo(() => new MessageSteering(client, options), [client, options.sessionId]);
useEffect(() => () => steering.destroy(), [steering]);
const state = useSyncExternalStore(
useCallback((cb) => steering.onStateChange(cb), [steering]),
() => steering.state,
() => INITIAL_STATE,
);
return {
submit: useCallback((text: string) => steering.submit(text), [steering]),
steer: useCallback((text: string) => steering.steer(text), [steering]),
queue: useCallback((text: string) => steering.queue(text), [steering]),
interrupt: useCallback((text: string) => steering.interrupt(text), [steering]),
flush: useCallback(() => steering.flush(), [steering]),
removeQueued: useCallback((i: number) => steering.removeQueued(i), [steering]),
clearQueued: useCallback(() => steering.clearQueued(), [steering]),
setMode: useCallback((m: SteeringMode) => steering.setMode(m), [steering]),
queued: state.queued,
isExecuting: state.isExecuting,
mode: state.mode,
};
}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import {
MessageLog,
type MessageLogOptions,
type MessageLogState,
type ChatMessage,
} from "@agentick/client";
import { useClient } from "./use-client";
export type UseMessagesOptions = MessageLogOptions;
export interface UseMessagesResult {
messages: readonly ChatMessage[];
clear: () => void;
}
const INITIAL_STATE: MessageLogState = {
messages: [],
};
/**
* Message accumulation hook — wraps `MessageLog` with `useSyncExternalStore`.
*
* Use standalone for message-only UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export function useMessages(options: UseMessagesOptions = {}): UseMessagesResult {
const client = useClient();
const log = useMemo(() => new MessageLog(client, options), [client, options.sessionId]);
useEffect(() => () => log.destroy(), [log]);
const state = useSyncExternalStore(
useCallback((cb) => log.onStateChange(cb), [log]),
() => log.state,
() => INITIAL_STATE,
);
return {
messages: state.messages,
clear: useCallback(() => log.clear(), [log]),
};
}
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import type { SessionAccessor } from "@agentick/client";
import { useClient } from "./use-client";
import type { UseSessionOptions, UseSessionResult } from "../types";
// ============================================================================
// useSession
// ============================================================================
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export function useSession(options: UseSessionOptions = {}): UseSessionResult {
const { sessionId, autoSubscribe = false } = options;
const client = useClient();
const mountedRef = useRef(true);
// Get or create session accessor
const accessor = useMemo<SessionAccessor | undefined>(() => {
if (!sessionId) return undefined;
return client.session(sessionId);
}, [client, sessionId]);
const [isSubscribed, setIsSubscribed] = useState(false);
// Cleanup on unmount
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
// Subscribe function
const subscribe = useCallback(() => {
if (!accessor) return;
accessor.subscribe();
if (mountedRef.current) {
setIsSubscribed(true);
}
}, [accessor]);
// Unsubscribe function
const unsubscribe = useCallback(() => {
if (!accessor) return;
accessor.unsubscribe();
if (mountedRef.current) {
setIsSubscribed(false);
}
}, [accessor]);
// Auto-subscribe
useEffect(() => {
if (autoSubscribe && accessor && !isSubscribed) {
subscribe();
}
}, [autoSubscribe, accessor, isSubscribed, subscribe]);
// Send function
const send = useCallback(
(input: Parameters<UseSessionResult["send"]>[0]) => {
if (accessor) {
const normalizedInput =
typeof input === "string"
? {
messages: [
{
role: "user" as const,
content: [{ type: "text" as const, text: input }],
},
],
}
: input;
return accessor.send(normalizedInput as any);
}
return client.send(input as any);
},
[client, accessor],
);
// Abort function
const abort = useCallback(
async (reason?: string) => {
if (accessor) {
await accessor.abort(reason);
} else if (sessionId) {
await client.abort(sessionId, reason);
}
},
[client, accessor, sessionId],
);
// Close function
const close = useCallback(async () => {
if (accessor) {
await accessor.close();
} else if (sessionId) {
await client.closeSession(sessionId);
}
}, [client, accessor, sessionId]);
return {
sessionId,
isSubscribed,
subscribe,
unsubscribe,
send,
abort,
close,
accessor,
};
}
import { useSyncExternalStore, useCallback } from "react";
import type { StreamingTextState } from "@agentick/client";
import { useClient } from "./use-client";
import type { UseStreamingTextOptions, UseStreamingTextResult } from "../types";
// ============================================================================
// useStreamingText
// ============================================================================
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export function useStreamingText(options: UseStreamingTextOptions = {}): UseStreamingTextResult {
const { enabled = true } = options;
const client = useClient();
// Use useSyncExternalStore for concurrent-safe subscription
const state = useSyncExternalStore<StreamingTextState>(
useCallback(
(onStoreChange) => {
if (!enabled) return () => {};
return client.onStreamingText(onStoreChange);
},
[client, enabled],
),
() => (enabled ? client.streamingText : { text: "", isStreaming: false }),
() => ({ text: "", isStreaming: false }),
);
const clear = useCallback(() => {
client.clearStreamingText();
}, [client]);
return { text: state.text, isStreaming: state.isStreaming, clear };
}
import { useMemo, useEffect, useSyncExternalStore, useCallback } from "react";
import {
ToolConfirmations,
type ToolConfirmationsOptions,
type ToolConfirmationsState,
type ToolConfirmationState,
} from "@agentick/client";
import type { ToolConfirmationResponse } from "@agentick/client";
import { useClient } from "./use-client";
export type UseToolConfirmationsOptions = ToolConfirmationsOptions;
export interface UseToolConfirmationsResult {
pending: ToolConfirmationState | null;
respond: (response: ToolConfirmationResponse) => void;
}
const INITIAL_STATE: ToolConfirmationsState = {
pending: null,
};
/**
* Tool confirmation hook — wraps `ToolConfirmations` with `useSyncExternalStore`.
*
* Use standalone for custom confirmation UIs, or use `useChat` for the full
* chat controller (messages + steering + confirmations).
*/
export function useToolConfirmations(
options: UseToolConfirmationsOptions = {},
): UseToolConfirmationsResult {
const client = useClient();
const tc = useMemo(() => new ToolConfirmations(client, options), [client, options.sessionId]);
useEffect(() => () => tc.destroy(), [tc]);
const state = useSyncExternalStore(
useCallback((cb) => tc.onStateChange(cb), [tc]),
() => tc.state,
() => INITIAL_STATE,
);
return {
pending: state.pending,
respond: useCallback((r: ToolConfirmationResponse) => tc.respond(r), [tc]),
};
}
+5
-3

@@ -70,3 +70,3 @@ /**

* | `useStreamingText(opts?)` | Accumulated text from deltas |
* | `useEvents(opts?)` | Stream event subscription |
* | `useLineEditor(opts)` | Readline-quality line editing (wraps client LineEditor) |
*

@@ -76,6 +76,8 @@ * @module @agentick/react

export { AgentickProvider } from "./context";
export { useClient, useConnection, useSession, useConnectionState, useEvents, useStreamingText, useContextInfo, type ContextInfo, type UseContextInfoOptions, type UseContextInfoResult, } from "./hooks";
export { useClient, useConnection, useSession, useConnectionState, useEvents, useStreamingText, useContextInfo, useMessageSteering, useMessages, useToolConfirmations, useChat, useLineEditor, type ContextInfo, type UseContextInfoOptions, type UseContextInfoResult, type UseMessageSteeringOptions, type UseMessageSteeringResult, type MessageSteeringState, type SteeringMode, type FlushMode, type UseMessagesOptions, type UseMessagesResult, type UseToolConfirmationsOptions, type UseToolConfirmationsResult, type UseChatOptions, type UseChatResult, type ChatMode, type ChatMessage, type ToolConfirmationState, type Attachment, type AttachmentInput, type UseLineEditorOptions, type LineEditorResult, } from "./hooks";
export type { AgentickProviderProps, AgentickContextValue, TransportConfig, UseConnectionOptions, UseConnectionResult, UseSessionOptions, UseSessionResult, UseEventsOptions, UseEventsResult, UseStreamingTextOptions, UseStreamingTextResult, } from "./types.js";
export type { AgentickClient, ConnectionState, StreamEvent, SessionAccessor, SendInput, ClientExecutionHandle, SessionStreamEvent, ClientTransport, } from "@agentick/client";
export type { AgentickClient, ConnectionState, StreamEvent, SessionAccessor, SendInput, ClientExecutionHandle, SessionStreamEvent, ClientTransport, LineEditorSnapshot, } from "@agentick/client";
export { LineEditor } from "@agentick/client";
export { createClient } from "@agentick/client";
export { timelineToMessages, extractToolCalls, defaultTransform, defaultDeriveMode, } from "@agentick/client";
//# sourceMappingURL=index.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,SAAS,CAAC;AAGjB,YAAY,EAEV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EAGf,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,OAAO,EACP,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,SAAS,CAAC;AAGjB,YAAY,EAEV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EAGf,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}

@@ -70,3 +70,3 @@ /**

* | `useStreamingText(opts?)` | Accumulated text from deltas |
* | `useEvents(opts?)` | Stream event subscription |
* | `useLineEditor(opts)` | Readline-quality line editing (wraps client LineEditor) |
*

@@ -78,5 +78,9 @@ * @module @agentick/react

// Hooks
export { useClient, useConnection, useSession, useConnectionState, useEvents, useStreamingText, useContextInfo, } from "./hooks";
export { useClient, useConnection, useSession, useConnectionState, useEvents, useStreamingText, useContextInfo, useMessageSteering, useMessages, useToolConfirmations, useChat, useLineEditor, } from "./hooks";
// Re-export LineEditor class for direct use
export { LineEditor } from "@agentick/client";
// Re-export createClient for users who want to create a client manually
export { createClient } from "@agentick/client";
// Re-export chat transform functions for history bootstrapping
export { timelineToMessages, extractToolCalls, defaultTransform, defaultDeriveMode, } from "@agentick/client";
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,WAAW;AACX,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,QAAQ;AACR,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,cAAc,GAIf,MAAM,SAAS,CAAC;AAgCjB,wEAAwE;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,WAAW;AACX,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,QAAQ;AACR,OAAO,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,OAAO,EACP,aAAa,GAsBd,MAAM,SAAS,CAAC;AAiCjB,4CAA4C;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,wEAAwE;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,+DAA+D;AAC/D,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}
{
"name": "@agentick/react",
"version": "0.4.0",
"version": "0.5.0",
"description": "React hooks and components for Agentick applications",

@@ -24,4 +24,4 @@ "repository": {

"dependencies": {
"@agentick/client": "0.4.0",
"@agentick/shared": "0.4.0"
"@agentick/client": "0.5.0",
"@agentick/shared": "0.6.0"
},

@@ -45,3 +45,3 @@ "devDependencies": {

"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"clean": "rm -rf dist tsconfig.build.tsbuildinfo",
"test": "vitest run --config vitest.config.ts",

@@ -48,0 +48,0 @@ "test:watch": "vitest --config vitest.config.ts"

+185
-0

@@ -220,2 +220,164 @@ # @agentick/react

### `useLineEditor(options)`
React wrapper around `@agentick/client`'s `LineEditor` class. Provides readline-quality editing with completion support via `useSyncExternalStore`.
```tsx
import { useLineEditor } from "@agentick/react";
const { value, cursor, completion, completedRanges, editor } = useLineEditor({
onSubmit: (text) => send(text),
});
// Register completion sources via the raw editor
useEffect(() => {
return editor.registerCompletion({
id: "file",
trigger: { type: "char", char: "#" },
resolve: async (query) => searchFiles(query),
});
}, [editor]);
```
Returns `{ value, cursor, completion, completedRanges, handleInput, setValue, clear, editor }`. The `editor` property is the raw `LineEditor` instance. The `completion` property is `CompletionState | null`.
For terminal UIs, use `useLineEditor` from `@agentick/tui` which adds Ink keystroke normalization. See [`COMPLETION.md`](../client/COMPLETION.md) for the full completion system reference.
### Chat Hooks
These hooks wrap the [Chat Primitives](../client/README.md#chat-primitives) from `@agentick/client`. See the client docs for the underlying `ChatSession`, `MessageLog`, `ToolConfirmations`, and `MessageSteering` classes.
#### `useChat(options?)`
Full chat controller hook — messages, steering, tool confirmations, and attachments in one call. Wraps [`ChatSession`](../client/README.md#chatsession) with `useSyncExternalStore`. Auto-subscribes to the SSE transport by default (set `autoSubscribe: false` to manage subscription separately via `useSession`).
```tsx
import { useChat } from "@agentick/react";
function Chat({ sessionId }: { sessionId: string }) {
const {
messages, // ChatMessage[]
chatMode, // "idle" | "streaming" | "confirming_tool"
toolConfirmation, // { request, respond } | null
lastSubmitted, // Optimistic user message text
queued, // Queued messages
isExecuting, // Execution in progress
mode, // "steer" | "queue"
attachments, // Attachment[] (pending, not yet sent)
submit, // Send or queue based on mode (drains attachments)
steer, // Always send immediately (drains attachments)
queue, // Always queue (no attachments)
interrupt, // Abort + send (drains attachments)
flush, // Flush next queued
respondToConfirmation,
clearMessages,
setMode,
removeQueued,
clearQueued,
addAttachment, // (input: AttachmentInput) => Attachment
removeAttachment, // (id: string) => void
clearAttachments, // () => void
} = useChat({ sessionId, mode: "queue" });
return (
<div>
{messages.map((msg) => (
<div key={msg.id} className={msg.role}>
{typeof msg.content === "string" ? msg.content : "..."}
</div>
))}
{toolConfirmation && (
<dialog open>
<p>Allow {toolConfirmation.request.name}?</p>
<button onClick={() => respondToConfirmation({ approved: true })}>Allow</button>
<button onClick={() => respondToConfirmation({ approved: false })}>Deny</button>
</dialog>
)}
<input
onKeyDown={(e) => {
if (e.key === "Enter") submit(e.currentTarget.value);
}}
/>
</div>
);
}
```
Options are captured at mount time. Changing them requires a new `sessionId`.
#### Custom Chat Modes
```tsx
type MyMode = "idle" | "working" | "needs_approval";
const { chatMode } = useChat<MyMode>({
sessionId,
deriveMode: ({ isExecuting, hasPendingConfirmation }) => {
if (hasPendingConfirmation) return "needs_approval";
if (isExecuting) return "working";
return "idle";
},
});
// chatMode is typed as MyMode
```
#### `useMessages(options?)`
Message accumulation only. Use when you don't need steering or confirmations.
```tsx
import { useMessages } from "@agentick/react";
const { messages, clear } = useMessages({
sessionId: "my-session",
transform: customTransform, // Optional custom MessageTransform
initialMessages: [], // Pre-loaded history
});
```
#### `useToolConfirmations(options?)`
Tool confirmation management only. Use for custom confirmation UIs.
```tsx
import { useToolConfirmations } from "@agentick/react";
const { pending, respond } = useToolConfirmations({
sessionId: "my-session",
policy: (req) => (req.name === "read_file" ? { action: "approve" } : { action: "prompt" }),
});
if (pending) {
// Show confirmation UI
respond({ approved: true });
}
```
#### `useMessageSteering(options?)`
Input-side message routing with queue/steer modes.
```tsx
import { useMessageSteering } from "@agentick/react";
const { queued, isExecuting, mode, submit, steer, queue, interrupt, flush, setMode } =
useMessageSteering({
sessionId: "my-session",
mode: "queue",
flushMode: "sequential",
});
```
#### Progressive Disclosure
| Level | Hook | Use case |
| ----- | ------------------------------------------------------------- | ------------------------------------ |
| 0 | `useChat` | Full chat — one hook does everything |
| 1 | `useChat` + options | Custom modes, policies, transforms |
| 2 | `useMessages` + `useToolConfirmations` + `useMessageSteering` | Compose individual primitives |
| 3 | `useEvents` + custom reducer | Full control, build your own |
## Provider

@@ -262,2 +424,17 @@

// Chat hook types
UseChatOptions,
UseChatResult,
UseMessagesOptions,
UseMessagesResult,
UseToolConfirmationsOptions,
UseToolConfirmationsResult,
UseMessageSteeringOptions,
UseMessageSteeringResult,
ChatMode,
ChatMessage,
ToolConfirmationState,
SteeringMode,
FlushMode,
// Re-exported from @agentick/client

@@ -273,2 +450,10 @@ AgentickClient,

} from "@agentick/react";
// Transform functions (re-exported from @agentick/client)
import {
timelineToMessages,
extractToolCalls,
defaultTransform,
defaultDeriveMode,
} from "@agentick/react";
```

@@ -275,0 +460,0 @@

@@ -70,3 +70,3 @@ /**

* | `useStreamingText(opts?)` | Accumulated text from deltas |
* | `useEvents(opts?)` | Stream event subscription |
* | `useLineEditor(opts)` | Readline-quality line editing (wraps client LineEditor) |
*

@@ -88,5 +88,28 @@ * @module @agentick/react

useContextInfo,
useMessageSteering,
useMessages,
useToolConfirmations,
useChat,
useLineEditor,
type ContextInfo,
type UseContextInfoOptions,
type UseContextInfoResult,
type UseMessageSteeringOptions,
type UseMessageSteeringResult,
type MessageSteeringState,
type SteeringMode,
type FlushMode,
type UseMessagesOptions,
type UseMessagesResult,
type UseToolConfirmationsOptions,
type UseToolConfirmationsResult,
type UseChatOptions,
type UseChatResult,
type ChatMode,
type ChatMessage,
type ToolConfirmationState,
type Attachment,
type AttachmentInput,
type UseLineEditorOptions,
type LineEditorResult,
} from "./hooks";

@@ -122,5 +145,17 @@

ClientTransport,
LineEditorSnapshot,
} from "@agentick/client";
// Re-export LineEditor class for direct use
export { LineEditor } from "@agentick/client";
// Re-export createClient for users who want to create a client manually
export { createClient } from "@agentick/client";
// Re-export chat transform functions for history bootstrapping
export {
timelineToMessages,
extractToolCalls,
defaultTransform,
defaultDeriveMode,
} from "@agentick/client";
/**
* React hooks for Agentick.
*
* @module @agentick/react/hooks
*/
import type { AgentickClient, ConnectionState } from "@agentick/client";
import type { ContextInfo } from "@agentick/shared";
import type { UseSessionOptions, UseSessionResult, UseEventsOptions, UseEventsResult, UseStreamingTextOptions, UseStreamingTextResult, UseConnectionOptions, UseConnectionResult } from "./types";
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export declare function useClient(): AgentickClient;
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export declare function useConnectionState(): ConnectionState;
/**
* Read the SSE connection state.
*/
export declare function useConnection(_options?: UseConnectionOptions): UseConnectionResult;
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export declare function useSession(options?: UseSessionOptions): UseSessionResult;
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export declare function useEvents(options?: UseEventsOptions): UseEventsResult;
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export declare function useStreamingText(options?: UseStreamingTextOptions): UseStreamingTextResult;
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export { type ContextInfo };
/**
* Options for useContextInfo hook.
*/
export interface UseContextInfoOptions {
/**
* Optional session ID to filter events for.
* If not provided, receives context info from any session.
*/
sessionId?: string;
/**
* Whether the hook is enabled.
* If false, no context info subscription is created.
* @default true
*/
enabled?: boolean;
}
/**
* Return value from useContextInfo hook.
*/
export interface UseContextInfoResult {
/**
* Latest context info (null before first tick completes).
*/
contextInfo: ContextInfo | null;
/**
* Clear the current context info.
*/
clear: () => void;
}
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export declare function useContextInfo(options?: UseContextInfoOptions): UseContextInfoResult;
//# sourceMappingURL=hooks.d.ts.map
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAKhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAMjB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,IAAI,cAAc,CAQ1C;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAcpD;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,GAAE,oBAAyB,GAAG,mBAAmB,CActF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAoG5E;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAqCzE;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,sBAAsB,CAsB9F;AAMD;;;GAGG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAEhC;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,CA+DxF"}
/**
* React hooks for Agentick.
*
* @module @agentick/react/hooks
*/
import { useState, useEffect, useCallback, useRef, useSyncExternalStore, useMemo, useContext, } from "react";
import { AgentickContext } from "./context";
// ============================================================================
// useClient
// ============================================================================
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export function useClient() {
const context = useContext(AgentickContext);
if (!context) {
throw new Error("useClient must be used within a AgentickProvider");
}
return context.client;
}
// ============================================================================
// useConnectionState (alias for useConnection)
// ============================================================================
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export function useConnectionState() {
const client = useClient();
const [state, setState] = useState(client.state);
useEffect(() => {
// Sync initial state
setState(client.state);
// Subscribe to changes
const unsubscribe = client.onConnectionChange(setState);
return unsubscribe;
}, [client]);
return state;
}
// ============================================================================
// useConnection
// ============================================================================
/**
* Read the SSE connection state.
*/
export function useConnection(_options = {}) {
const client = useClient();
const [state, setState] = useState(client.state);
useEffect(() => {
setState(client.state);
return client.onConnectionChange(setState);
}, [client]);
return {
state,
isConnected: state === "connected",
isConnecting: state === "connecting",
};
}
// ============================================================================
// useSession
// ============================================================================
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export function useSession(options = {}) {
const { sessionId, autoSubscribe = false } = options;
const client = useClient();
const mountedRef = useRef(true);
// Get or create session accessor
const accessor = useMemo(() => {
if (!sessionId)
return undefined;
return client.session(sessionId);
}, [client, sessionId]);
const [isSubscribed, setIsSubscribed] = useState(false);
// Cleanup on unmount
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
// Subscribe function
const subscribe = useCallback(() => {
if (!accessor)
return;
accessor.subscribe();
if (mountedRef.current) {
setIsSubscribed(true);
}
}, [accessor]);
// Unsubscribe function
const unsubscribe = useCallback(() => {
if (!accessor)
return;
accessor.unsubscribe();
if (mountedRef.current) {
setIsSubscribed(false);
}
}, [accessor]);
// Auto-subscribe
useEffect(() => {
if (autoSubscribe && accessor && !isSubscribed) {
subscribe();
}
}, [autoSubscribe, accessor, isSubscribed, subscribe]);
// Send function
const send = useCallback((input) => {
if (accessor) {
const normalizedInput = typeof input === "string"
? {
messages: [
{
role: "user",
content: [{ type: "text", text: input }],
},
],
}
: input;
return accessor.send(normalizedInput);
}
return client.send(input);
}, [client, accessor]);
// Abort function
const abort = useCallback(async (reason) => {
if (accessor) {
await accessor.abort(reason);
}
else if (sessionId) {
await client.abort(sessionId, reason);
}
}, [client, accessor, sessionId]);
// Close function
const close = useCallback(async () => {
if (accessor) {
await accessor.close();
}
else if (sessionId) {
await client.closeSession(sessionId);
}
}, [client, accessor, sessionId]);
return {
sessionId,
isSubscribed,
subscribe,
unsubscribe,
send,
abort,
close,
accessor,
};
}
// ============================================================================
// useEvents
// ============================================================================
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export function useEvents(options = {}) {
const { filter, sessionId, enabled = true } = options;
const client = useClient();
const [event, setEvent] = useState();
useEffect(() => {
if (!enabled)
return;
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
const unsubscribe = accessor.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}
// Global subscription
const unsubscribe = client.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}, [client, sessionId, enabled, filter]);
const clear = useCallback(() => {
setEvent(undefined);
}, []);
return { event, clear };
}
// ============================================================================
// useStreamingText
// ============================================================================
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export function useStreamingText(options = {}) {
const { enabled = true } = options;
const client = useClient();
// Use useSyncExternalStore for concurrent-safe subscription
const state = useSyncExternalStore(useCallback((onStoreChange) => {
if (!enabled)
return () => { };
return client.onStreamingText(onStoreChange);
}, [client, enabled]), () => (enabled ? client.streamingText : { text: "", isStreaming: false }), () => ({ text: "", isStreaming: false }));
const clear = useCallback(() => {
client.clearStreamingText();
}, [client]);
return { text: state.text, isStreaming: state.isStreaming, clear };
}
// ============================================================================
// useContextInfo
// ============================================================================
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export {};
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export function useContextInfo(options = {}) {
const { sessionId, enabled = true } = options;
const client = useClient();
const [contextInfo, setContextInfo] = useState(null);
useEffect(() => {
if (!enabled)
return;
// Subscribe to context_update events
const handleEvent = (event) => {
if (event.type !== "context_update")
return;
// Type assertion since we filtered by type
const ctxEvent = event;
setContextInfo({
modelId: ctxEvent.modelId,
modelName: ctxEvent.modelName,
provider: ctxEvent.provider,
contextWindow: ctxEvent.contextWindow,
inputTokens: ctxEvent.inputTokens,
outputTokens: ctxEvent.outputTokens,
totalTokens: ctxEvent.totalTokens,
utilization: ctxEvent.utilization,
maxOutputTokens: ctxEvent.maxOutputTokens,
supportsVision: ctxEvent.supportsVision,
supportsToolUse: ctxEvent.supportsToolUse,
isReasoningModel: ctxEvent.isReasoningModel,
tick: ctxEvent.tick,
cumulativeUsage: ctxEvent.cumulativeUsage,
});
};
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
return accessor.onEvent(handleEvent);
}
// Global subscription
return client.onEvent(handleEvent);
}, [client, sessionId, enabled]);
const clear = useCallback(() => {
setContextInfo(null);
}, []);
return { contextInfo, clear };
}
//# sourceMappingURL=hooks.js.map
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
/**
* React hooks for Agentick.
*
* @module @agentick/react/hooks
*/
import {
useState,
useEffect,
useCallback,
useRef,
useSyncExternalStore,
useMemo,
useContext,
} from "react";
import type {
AgentickClient,
ConnectionState,
StreamEvent,
SessionStreamEvent,
StreamingTextState,
SessionAccessor,
} from "@agentick/client";
import type { ContextInfo } from "@agentick/shared";
import { AgentickContext } from "./context";
import type {
UseSessionOptions,
UseSessionResult,
UseEventsOptions,
UseEventsResult,
UseStreamingTextOptions,
UseStreamingTextResult,
UseConnectionOptions,
UseConnectionResult,
} from "./types";
// ============================================================================
// useClient
// ============================================================================
/**
* Access the Agentick client from context.
*
* @throws If used outside of AgentickProvider
*
* @example
* ```tsx
* import { useClient } from '@agentick/react';
*
* function MyComponent() {
* const client = useClient();
*
* // Direct client access for advanced use cases
* const handleCustomChannel = () => {
* const session = client.session('conv-123');
* const channel = session.channel('custom');
* channel.publish('event', { data: 'value' });
* };
*
* return <button onClick={handleCustomChannel}>Send</button>;
* }
* ```
*/
export function useClient(): AgentickClient {
const context = useContext(AgentickContext);
if (!context) {
throw new Error("useClient must be used within a AgentickProvider");
}
return context.client;
}
// ============================================================================
// useConnectionState (alias for useConnection)
// ============================================================================
/**
* Subscribe to connection state changes.
*
* @example
* ```tsx
* import { useConnectionState } from '@agentick/react';
*
* function ConnectionIndicator() {
* const state = useConnectionState();
*
* return (
* <div className={`indicator ${state}`}>
* {state === 'connected' ? 'Online' : 'Offline'}
* </div>
* );
* }
* ```
*/
export function useConnectionState(): ConnectionState {
const client = useClient();
const [state, setState] = useState<ConnectionState>(client.state);
useEffect(() => {
// Sync initial state
setState(client.state);
// Subscribe to changes
const unsubscribe = client.onConnectionChange(setState);
return unsubscribe;
}, [client]);
return state;
}
// ============================================================================
// useConnection
// ============================================================================
/**
* Read the SSE connection state.
*/
export function useConnection(_options: UseConnectionOptions = {}): UseConnectionResult {
const client = useClient();
const [state, setState] = useState<ConnectionState>(client.state);
useEffect(() => {
setState(client.state);
return client.onConnectionChange(setState);
}, [client]);
return {
state,
isConnected: state === "connected",
isConnecting: state === "connecting",
};
}
// ============================================================================
// useSession
// ============================================================================
/**
* Work with a specific session.
*
* @example Basic usage with session ID
* ```tsx
* import { useSession } from '@agentick/react';
*
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed, subscribe } = useSession({ sessionId });
* const [input, setInput] = useState('');
*
* // Subscribe on mount
* useEffect(() => {
* subscribe();
* }, [subscribe]);
*
* const handleSend = async () => {
* await send(input);
* setInput('');
* };
*
* return (
* <div>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <button onClick={handleSend}>Send</button>
* </div>
* );
* }
* ```
*
* @example Ephemeral session (no sessionId)
* ```tsx
* function QuickChat() {
* const { send } = useSession();
*
* // Each send creates/uses an ephemeral session
* const handleSend = () => send('Hello!');
*
* return <button onClick={handleSend}>Ask</button>;
* }
* ```
*
* @example Auto-subscribe
* ```tsx
* function Chat({ sessionId }: { sessionId: string }) {
* const { send, isSubscribed } = useSession({
* sessionId,
* autoSubscribe: true,
* });
*
* if (!isSubscribed) return <div>Subscribing...</div>;
*
* return <ChatInterface />;
* }
* ```
*/
export function useSession(options: UseSessionOptions = {}): UseSessionResult {
const { sessionId, autoSubscribe = false } = options;
const client = useClient();
const mountedRef = useRef(true);
// Get or create session accessor
const accessor = useMemo<SessionAccessor | undefined>(() => {
if (!sessionId) return undefined;
return client.session(sessionId);
}, [client, sessionId]);
const [isSubscribed, setIsSubscribed] = useState(false);
// Cleanup on unmount
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
// Subscribe function
const subscribe = useCallback(() => {
if (!accessor) return;
accessor.subscribe();
if (mountedRef.current) {
setIsSubscribed(true);
}
}, [accessor]);
// Unsubscribe function
const unsubscribe = useCallback(() => {
if (!accessor) return;
accessor.unsubscribe();
if (mountedRef.current) {
setIsSubscribed(false);
}
}, [accessor]);
// Auto-subscribe
useEffect(() => {
if (autoSubscribe && accessor && !isSubscribed) {
subscribe();
}
}, [autoSubscribe, accessor, isSubscribed, subscribe]);
// Send function
const send = useCallback(
(input: Parameters<UseSessionResult["send"]>[0]) => {
if (accessor) {
const normalizedInput =
typeof input === "string"
? {
messages: [
{
role: "user" as const,
content: [{ type: "text" as const, text: input }],
},
],
}
: input;
return accessor.send(normalizedInput as any);
}
return client.send(input as any);
},
[client, accessor],
);
// Abort function
const abort = useCallback(
async (reason?: string) => {
if (accessor) {
await accessor.abort(reason);
} else if (sessionId) {
await client.abort(sessionId, reason);
}
},
[client, accessor, sessionId],
);
// Close function
const close = useCallback(async () => {
if (accessor) {
await accessor.close();
} else if (sessionId) {
await client.closeSession(sessionId);
}
}, [client, accessor, sessionId]);
return {
sessionId,
isSubscribed,
subscribe,
unsubscribe,
send,
abort,
close,
accessor,
};
}
// ============================================================================
// useEvents
// ============================================================================
/**
* Subscribe to stream events.
*
* Returns the latest event (not accumulated). Use useStreamingText
* for accumulated text from content_delta events.
*
* @example
* ```tsx
* import { useEvents } from '@agentick/react';
*
* function EventLog() {
* const { event } = useEvents();
*
* useEffect(() => {
* if (event) {
* console.log('Event:', event.type, event);
* }
* }, [event]);
*
* return <div>Latest: {event?.type}</div>;
* }
* ```
*
* @example With filter
* ```tsx
* function ToolCalls() {
* const { event } = useEvents({ filter: ['tool_call', 'tool_result'] });
*
* if (!event) return null;
*
* return <div>Tool: {event.type === 'tool_call' ? event.name : 'result'}</div>;
* }
* ```
*
* @example Session-specific events
* ```tsx
* function SessionEvents({ sessionId }: { sessionId: string }) {
* const { event } = useEvents({ sessionId });
* // Only receives events for this session
* return <div>{event?.type}</div>;
* }
* ```
*/
export function useEvents(options: UseEventsOptions = {}): UseEventsResult {
const { filter, sessionId, enabled = true } = options;
const client = useClient();
const [event, setEvent] = useState<StreamEvent | SessionStreamEvent | undefined>();
useEffect(() => {
if (!enabled) return;
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
const unsubscribe = accessor.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}
// Global subscription
const unsubscribe = client.onEvent((incoming) => {
if (filter && !filter.includes(incoming.type)) {
return;
}
setEvent(incoming);
});
return unsubscribe;
}, [client, sessionId, enabled, filter]);
const clear = useCallback(() => {
setEvent(undefined);
}, []);
return { event, clear };
}
// ============================================================================
// useStreamingText
// ============================================================================
/**
* Subscribe to streaming text from the client.
*
* Uses the client's built-in streaming text accumulation which handles
* tick_start, content_delta, tick_end, and execution_end events.
*
* @example
* ```tsx
* import { useStreamingText } from '@agentick/react';
*
* function StreamingResponse() {
* const { text, isStreaming } = useStreamingText();
*
* return (
* <div>
* <p>{text}</p>
* {isStreaming && <span className="cursor">|</span>}
* </div>
* );
* }
* ```
*/
export function useStreamingText(options: UseStreamingTextOptions = {}): UseStreamingTextResult {
const { enabled = true } = options;
const client = useClient();
// Use useSyncExternalStore for concurrent-safe subscription
const state = useSyncExternalStore<StreamingTextState>(
useCallback(
(onStoreChange) => {
if (!enabled) return () => {};
return client.onStreamingText(onStoreChange);
},
[client, enabled],
),
() => (enabled ? client.streamingText : { text: "", isStreaming: false }),
() => ({ text: "", isStreaming: false }),
);
const clear = useCallback(() => {
client.clearStreamingText();
}, [client]);
return { text: state.text, isStreaming: state.isStreaming, clear };
}
// ============================================================================
// useContextInfo
// ============================================================================
/**
* Context utilization info from the server.
* Updated after each tick with token usage and model capabilities.
*/
export { type ContextInfo };
/**
* Options for useContextInfo hook.
*/
export interface UseContextInfoOptions {
/**
* Optional session ID to filter events for.
* If not provided, receives context info from any session.
*/
sessionId?: string;
/**
* Whether the hook is enabled.
* If false, no context info subscription is created.
* @default true
*/
enabled?: boolean;
}
/**
* Return value from useContextInfo hook.
*/
export interface UseContextInfoResult {
/**
* Latest context info (null before first tick completes).
*/
contextInfo: ContextInfo | null;
/**
* Clear the current context info.
*/
clear: () => void;
}
/**
* Subscribe to context utilization info from the server.
*
* Receives context_update events after each tick with:
* - Token usage (input, output, total)
* - Context utilization percentage
* - Model capabilities (vision, tools, reasoning)
* - Cumulative usage across ticks
*
* @example Basic usage
* ```tsx
* import { useContextInfo } from '@agentick/react';
*
* function ContextBar() {
* const { contextInfo } = useContextInfo();
*
* if (!contextInfo) return null;
*
* return (
* <div className="context-bar">
* <span>{contextInfo.modelId}</span>
* <span>{contextInfo.utilization?.toFixed(1)}% used</span>
* <progress value={contextInfo.utilization} max={100} />
* </div>
* );
* }
* ```
*
* @example Session-specific context info
* ```tsx
* function SessionContext({ sessionId }: { sessionId: string }) {
* const { contextInfo } = useContextInfo({ sessionId });
*
* if (!contextInfo) return <span>No context yet</span>;
*
* return (
* <span>
* {contextInfo.inputTokens.toLocaleString()} /
* {contextInfo.contextWindow?.toLocaleString() ?? '?'} tokens
* </span>
* );
* }
* ```
*/
export function useContextInfo(options: UseContextInfoOptions = {}): UseContextInfoResult {
const { sessionId, enabled = true } = options;
const client = useClient();
const [contextInfo, setContextInfo] = useState<ContextInfo | null>(null);
useEffect(() => {
if (!enabled) return;
// Subscribe to context_update events
const handleEvent = (event: StreamEvent | SessionStreamEvent) => {
if (event.type !== "context_update") return;
// Type assertion since we filtered by type
const ctxEvent = event as StreamEvent & {
modelId: string;
modelName?: string;
provider?: string;
contextWindow?: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
utilization?: number;
maxOutputTokens?: number;
supportsVision?: boolean;
supportsToolUse?: boolean;
isReasoningModel?: boolean;
tick: number;
cumulativeUsage?: ContextInfo["cumulativeUsage"];
};
setContextInfo({
modelId: ctxEvent.modelId,
modelName: ctxEvent.modelName,
provider: ctxEvent.provider,
contextWindow: ctxEvent.contextWindow,
inputTokens: ctxEvent.inputTokens,
outputTokens: ctxEvent.outputTokens,
totalTokens: ctxEvent.totalTokens,
utilization: ctxEvent.utilization,
maxOutputTokens: ctxEvent.maxOutputTokens,
supportsVision: ctxEvent.supportsVision,
supportsToolUse: ctxEvent.supportsToolUse,
isReasoningModel: ctxEvent.isReasoningModel,
tick: ctxEvent.tick,
cumulativeUsage: ctxEvent.cumulativeUsage,
});
};
// Use session-specific subscription if sessionId provided
if (sessionId) {
const accessor = client.session(sessionId);
return accessor.onEvent(handleEvent);
}
// Global subscription
return client.onEvent(handleEvent);
}, [client, sessionId, enabled]);
const clear = useCallback(() => {
setContextInfo(null);
}, []);
return { contextInfo, clear };
}