import { z } from 'zod'; import { type APIClient } from '../api.ts'; import { StreamResponseError } from './util.ts'; // --- Schemas --- export const NamespaceInfoSchema = z.object({ name: z.string().describe('the namespace name'), count: z.number().describe('number of streams in this namespace'), total_size: z.number().describe('total size in bytes across all streams'), created_at: z.string().describe('ISO 8601 timestamp of the earliest stream'), last_used_at: z.string().describe('ISO 8601 timestamp of the most recent activity'), internal: z.boolean().describe('whether this namespace contains internal/system streams'), }); export type NamespaceInfo = z.infer; // Response schema matching Pulse's format (NO data wrapper) const ListNamespacesResponseSchema = z.discriminatedUnion('success', [ z.object({ success: z.literal(false), message: z.string().optional(), }), z.object({ success: z.literal(true), namespaces: z.array(NamespaceInfoSchema), total: z.number(), }), ]); type ListNamespacesResponse = z.infer; export const StreamNamespaceEntrySchema = z.object({ id: z.string().describe('the stream entry id'), name: z.string().describe('the stream namespace name'), created_at: z.string().nullable().describe('ISO 8601 creation timestamp'), updated_at: z.string().nullable().describe('ISO 8601 last update timestamp'), org_id: z.string().describe('the organization id'), project_id: z.string().nullable().describe('the project id'), chunks: z.number().describe('number of chunks'), completed: z.boolean().describe('whether the stream upload is completed'), size_bytes: z.number().describe('size in bytes'), started_at: z.string().nullable().optional().describe('ISO 8601 stream start timestamp'), ended_at: z.string().nullable().describe('ISO 8601 stream end timestamp'), headers: z.record(z.string(), z.string()).nullable().optional().describe('stream headers'), metadata: z.record(z.string(), z.string()).nullable().optional().describe('stream metadata'), expires_at: z.string().nullable().optional().describe('ISO 8601 expiration timestamp or null'), url: z.string().describe('public URL to access the stream'), }); export type StreamNamespaceEntry = z.infer; const GetNamespaceResponseSchema = z.discriminatedUnion('success', [ z.object({ success: z.literal(false), message: z.string().optional(), }), z.object({ success: z.literal(true), name: z.string(), entries: z.array(StreamNamespaceEntrySchema), total: z.number(), total_size: z.number(), }), ]); type GetNamespaceResponse = z.infer; // --- Options --- export interface StreamListNamespacesOptions { limit?: number; offset?: number; } export interface StreamGetNamespaceOptions { limit?: number; offset?: number; } // --- Return types --- export interface StreamNamespacesResult { namespaces: NamespaceInfo[]; total: number; } export interface StreamNamespaceResult { name: string; entries: StreamNamespaceEntry[]; total: number; totalSize: number; } // --- Functions --- /** * List stream namespaces (aggregated view). * Each namespace groups streams by name, showing count, total size, and timestamps. * * @param client - The API client configured for Pulse * @param options - Pagination options * @returns Aggregated namespace list with total count * * @example * const result = await streamListNamespaces(client, { limit: 50 }); * console.log(`Found ${result.total} namespaces`); */ export async function streamListNamespaces( client: APIClient, options: StreamListNamespacesOptions = {} ): Promise { const resp = await client.post( '/namespaces', { limit: options.limit, offset: options.offset }, ListNamespacesResponseSchema ); if (resp.success) { return { namespaces: resp.namespaces, total: resp.total }; } throw new StreamResponseError({ message: resp.message }); } /** * Get entries within a specific stream namespace. * Returns full detail for each stream entry in the namespace. * * @param client - The API client configured for Pulse * @param name - The namespace name * @param options - Pagination options * @returns Namespace entries with total count and size * * @example * const result = await streamGetNamespace(client, 'agent-logs', { limit: 100 }); * console.log(`${result.total} entries, ${result.totalSize} bytes total`); */ export async function streamGetNamespace( client: APIClient, name: string, options: StreamGetNamespaceOptions = {} ): Promise { const resp = await client.post( `/namespace/${encodeURIComponent(name)}/info`, { limit: options.limit, offset: options.offset }, GetNamespaceResponseSchema ); if (resp.success) { return { name: resp.name, entries: resp.entries, total: resp.total, totalSize: resp.total_size, }; } throw new StreamResponseError({ message: resp.message }); }