Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:37 +08:00
commit 13df4850f7
29 changed files with 6729 additions and 0 deletions

View File

@@ -0,0 +1,409 @@
/**
* Streaming Utilities for TheSys C1
*
* Helper functions for handling streaming responses from
* OpenAI SDK, TheSys API, and transforming streams for C1.
*
* Works with any framework (Vite, Next.js, Cloudflare Workers).
*/
/**
* Convert a ReadableStream to a string
*/
export async function streamToString(stream: ReadableStream<string>): Promise<string> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let result = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// value might be string or Uint8Array
if (typeof value === "string") {
result += value;
} else {
result += decoder.decode(value, { stream: true });
}
}
// Final decode with stream: false
result += decoder.decode();
return result;
} finally {
reader.releaseLock();
}
}
/**
* Convert a ReadableStream to an array of chunks
*/
export async function streamToArray<T>(stream: ReadableStream<T>): Promise<T[]> {
const reader = stream.getReader();
const chunks: T[] = [];
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
return chunks;
} finally {
reader.releaseLock();
}
}
/**
* Create a pass-through stream that allows reading while data flows
*/
export function createPassThroughStream<T>(): {
readable: ReadableStream<T>;
writable: WritableStream<T>;
} {
const { readable, writable } = new TransformStream<T, T>();
return { readable, writable };
}
/**
* Transform a stream with a callback function
* Similar to @crayonai/stream's transformStream
*/
export function transformStream<TInput, TOutput>(
source: ReadableStream<TInput>,
transformer: (chunk: TInput) => TOutput | null,
options?: {
onStart?: () => void;
onEnd?: (data: { accumulated: TOutput[] }) => void;
onError?: (error: Error) => void;
}
): ReadableStream<TOutput> {
const accumulated: TOutput[] = [];
return new ReadableStream<TOutput>({
async start(controller) {
options?.onStart?.();
const reader = source.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
options?.onEnd?.({ accumulated });
controller.close();
break;
}
const transformed = transformer(value);
if (transformed !== null) {
accumulated.push(transformed);
controller.enqueue(transformed);
}
}
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
options?.onError?.(err);
controller.error(err);
} finally {
reader.releaseLock();
}
},
});
}
/**
* Merge multiple streams into one
*/
export function mergeStreams<T>(...streams: ReadableStream<T>[]): ReadableStream<T> {
return new ReadableStream<T>({
async start(controller) {
try {
await Promise.all(
streams.map(async (stream) => {
const reader = stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
controller.enqueue(value);
}
} finally {
reader.releaseLock();
}
})
);
controller.close();
} catch (error) {
controller.error(error);
}
},
});
}
/**
* Split a stream into multiple streams
*/
export function splitStream<T>(
source: ReadableStream<T>,
count: number
): ReadableStream<T>[] {
if (count < 2) throw new Error("Count must be at least 2");
const readers: ReadableStreamDefaultController<T>[] = [];
const streams = Array.from({ length: count }, () => {
return new ReadableStream<T>({
start(controller) {
readers.push(controller);
},
});
});
(async () => {
const reader = source.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
readers.forEach((r) => r.close());
break;
}
readers.forEach((r) => r.enqueue(value));
}
} catch (error) {
readers.forEach((r) => r.error(error));
} finally {
reader.releaseLock();
}
})();
return streams;
}
/**
* Buffer chunks until a condition is met, then flush
*/
export function bufferStream<T>(
source: ReadableStream<T>,
shouldFlush: (buffer: T[]) => boolean
): ReadableStream<T[]> {
return new ReadableStream<T[]>({
async start(controller) {
const reader = source.getReader();
let buffer: T[] = [];
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer.length > 0) {
controller.enqueue([...buffer]);
}
controller.close();
break;
}
buffer.push(value);
if (shouldFlush(buffer)) {
controller.enqueue([...buffer]);
buffer = [];
}
}
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
}
/**
* Rate limit a stream (delay between chunks)
*/
export function rateLimit<T>(
source: ReadableStream<T>,
delayMs: number
): ReadableStream<T> {
return new ReadableStream<T>({
async start(controller) {
const reader = source.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
// Wait before next chunk
if (delayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
}
/**
* Retry a stream creation if it fails
*/
export async function retryStream<T>(
createStream: () => Promise<ReadableStream<T>>,
maxRetries: number = 3,
delayMs: number = 1000
): Promise<ReadableStream<T>> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await createStream();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
console.error(`Stream creation attempt ${attempt + 1} failed:`, lastError);
if (attempt < maxRetries - 1) {
// Exponential backoff
const waitTime = delayMs * Math.pow(2, attempt);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
}
throw lastError || new Error("Failed to create stream");
}
/**
* Parse Server-Sent Events (SSE) stream
*/
export function parseSSE(
source: ReadableStream<Uint8Array>
): ReadableStream<{ event?: string; data: string }> {
const decoder = new TextDecoder();
let buffer = "";
return new ReadableStream({
async start(controller) {
const reader = source.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
let event = "";
let data = "";
for (const line of lines) {
if (line.startsWith("event:")) {
event = line.slice(6).trim();
} else if (line.startsWith("data:")) {
data += line.slice(5).trim();
} else if (line === "") {
// Empty line signals end of message
if (data) {
controller.enqueue({ event: event || undefined, data });
event = "";
data = "";
}
}
}
}
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
}
/**
* Handle backpressure in streams
*/
export function handleBackpressure<T>(
source: ReadableStream<T>,
highWaterMark: number = 10
): ReadableStream<T> {
return new ReadableStream<T>(
{
async start(controller) {
const reader = source.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
controller.enqueue(value);
// Check if we need to apply backpressure
if (controller.desiredSize !== null && controller.desiredSize <= 0) {
// Wait a bit before continuing
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
},
{ highWaterMark }
);
}
/**
* Log stream chunks for debugging
*/
export function debugStream<T>(
source: ReadableStream<T>,
label: string = "Stream"
): ReadableStream<T> {
let count = 0;
return transformStream(
source,
(chunk) => {
console.log(`[${label}] Chunk ${++count}:`, chunk);
return chunk;
},
{
onStart: () => console.log(`[${label}] Stream started`),
onEnd: ({ accumulated }) =>
console.log(`[${label}] Stream ended. Total chunks: ${accumulated.length}`),
onError: (error) => console.error(`[${label}] Stream error:`, error),
}
);
}

View File

@@ -0,0 +1,318 @@
/**
* Reusable Theme Configurations for TheSys C1
*
* Collection of custom theme objects that can be used across
* any framework (Vite, Next.js, Cloudflare Workers).
*
* Usage:
* import { darkTheme, lightTheme, oceanTheme } from "./theme-config";
*
* <C1Chat theme={oceanTheme} />
*/
export interface C1Theme {
mode: "light" | "dark";
colors: {
primary: string;
secondary: string;
background: string;
foreground: string;
border: string;
muted: string;
accent: string;
destructive?: string;
success?: string;
warning?: string;
};
fonts: {
body: string;
heading: string;
mono?: string;
};
borderRadius: string;
spacing: {
base: string;
};
}
// ============================================================================
// Light Themes
// ============================================================================
export const lightTheme: C1Theme = {
mode: "light",
colors: {
primary: "#3b82f6", // Blue
secondary: "#8b5cf6", // Purple
background: "#ffffff",
foreground: "#1f2937",
border: "#e5e7eb",
muted: "#f3f4f6",
accent: "#10b981", // Green
destructive: "#ef4444", // Red
success: "#10b981", // Green
warning: "#f59e0b", // Amber
},
fonts: {
body: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
heading: "'Inter', sans-serif",
mono: "'Fira Code', 'Courier New', monospace",
},
borderRadius: "8px",
spacing: {
base: "16px",
},
};
export const oceanTheme: C1Theme = {
mode: "light",
colors: {
primary: "#0ea5e9", // Sky blue
secondary: "#06b6d4", // Cyan
background: "#f0f9ff",
foreground: "#0c4a6e",
border: "#bae6fd",
muted: "#e0f2fe",
accent: "#0891b2",
destructive: "#dc2626",
success: "#059669",
warning: "#d97706",
},
fonts: {
body: "'Nunito', sans-serif",
heading: "'Nunito', sans-serif",
mono: "'JetBrains Mono', monospace",
},
borderRadius: "12px",
spacing: {
base: "16px",
},
};
export const sunsetTheme: C1Theme = {
mode: "light",
colors: {
primary: "#f59e0b", // Amber
secondary: "#f97316", // Orange
background: "#fffbeb",
foreground: "#78350f",
border: "#fed7aa",
muted: "#fef3c7",
accent: "#ea580c",
destructive: "#dc2626",
success: "#16a34a",
warning: "#f59e0b",
},
fonts: {
body: "'Poppins', sans-serif",
heading: "'Poppins', sans-serif",
mono: "'Source Code Pro', monospace",
},
borderRadius: "6px",
spacing: {
base: "16px",
},
};
// ============================================================================
// Dark Themes
// ============================================================================
export const darkTheme: C1Theme = {
mode: "dark",
colors: {
primary: "#60a5fa", // Light blue
secondary: "#a78bfa", // Light purple
background: "#111827",
foreground: "#f9fafb",
border: "#374151",
muted: "#1f2937",
accent: "#34d399",
destructive: "#f87171",
success: "#34d399",
warning: "#fbbf24",
},
fonts: {
body: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
heading: "'Inter', sans-serif",
mono: "'Fira Code', 'Courier New', monospace",
},
borderRadius: "8px",
spacing: {
base: "16px",
},
};
export const midnightTheme: C1Theme = {
mode: "dark",
colors: {
primary: "#818cf8", // Indigo
secondary: "#c084fc", // Purple
background: "#0f172a",
foreground: "#e2e8f0",
border: "#334155",
muted: "#1e293b",
accent: "#8b5cf6",
destructive: "#f87171",
success: "#4ade80",
warning: "#facc15",
},
fonts: {
body: "'Roboto', sans-serif",
heading: "'Roboto', sans-serif",
mono: "'IBM Plex Mono', monospace",
},
borderRadius: "10px",
spacing: {
base: "16px",
},
};
export const forestTheme: C1Theme = {
mode: "dark",
colors: {
primary: "#4ade80", // Green
secondary: "#22d3ee", // Cyan
background: "#064e3b",
foreground: "#d1fae5",
border: "#065f46",
muted: "#047857",
accent: "#10b981",
destructive: "#fca5a5",
success: "#6ee7b7",
warning: "#fde047",
},
fonts: {
body: "'Lato', sans-serif",
heading: "'Lato', sans-serif",
mono: "'Consolas', monospace",
},
borderRadius: "8px",
spacing: {
base: "18px",
},
};
// ============================================================================
// High Contrast Themes (Accessibility)
// ============================================================================
export const highContrastLight: C1Theme = {
mode: "light",
colors: {
primary: "#0000ff", // Pure blue
secondary: "#ff00ff", // Pure magenta
background: "#ffffff",
foreground: "#000000",
border: "#000000",
muted: "#f5f5f5",
accent: "#008000", // Pure green
destructive: "#ff0000",
success: "#008000",
warning: "#ff8800",
},
fonts: {
body: "'Arial', sans-serif",
heading: "'Arial', bold, sans-serif",
mono: "'Courier New', monospace",
},
borderRadius: "2px",
spacing: {
base: "20px",
},
};
export const highContrastDark: C1Theme = {
mode: "dark",
colors: {
primary: "#00ccff", // Bright cyan
secondary: "#ff00ff", // Bright magenta
background: "#000000",
foreground: "#ffffff",
border: "#ffffff",
muted: "#1a1a1a",
accent: "#00ff00", // Bright green
destructive: "#ff0000",
success: "#00ff00",
warning: "#ffaa00",
},
fonts: {
body: "'Arial', sans-serif",
heading: "'Arial', bold, sans-serif",
mono: "'Courier New', monospace",
},
borderRadius: "2px",
spacing: {
base: "20px",
},
};
// ============================================================================
// Theme Utilities
// ============================================================================
/**
* Get system theme preference
*/
export function getSystemTheme(): "light" | "dark" {
if (typeof window === "undefined") return "light";
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
/**
* Listen to system theme changes
*/
export function onSystemThemeChange(callback: (theme: "light" | "dark") => void) {
if (typeof window === "undefined") return () => {};
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent) => {
callback(e.matches ? "dark" : "light");
};
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}
/**
* Get theme based on user preference
*/
export function getTheme(
preference: "light" | "dark" | "system",
lightThemeConfig: C1Theme = lightTheme,
darkThemeConfig: C1Theme = darkTheme
): C1Theme {
if (preference === "system") {
const systemPref = getSystemTheme();
return systemPref === "dark" ? darkThemeConfig : lightThemeConfig;
}
return preference === "dark" ? darkThemeConfig : lightThemeConfig;
}
/**
* All available themes by name
*/
export const themes = {
light: lightTheme,
dark: darkTheme,
ocean: oceanTheme,
sunset: sunsetTheme,
midnight: midnightTheme,
forest: forestTheme,
"high-contrast-light": highContrastLight,
"high-contrast-dark": highContrastDark,
} as const;
export type ThemeName = keyof typeof themes;
/**
* Get theme by name
*/
export function getThemeByName(name: ThemeName): C1Theme {
return themes[name];
}

View File

@@ -0,0 +1,327 @@
/**
* Common Zod Schemas for Tool Calling
*
* Reusable schemas for common tools across any framework.
* These schemas provide runtime validation and type safety.
*
* Usage:
* import { webSearchTool, createOrderTool } from "./tool-schemas";
* import zodToJsonSchema from "zod-to-json-schema";
*
* const tools = [webSearchTool, createOrderTool];
*
* await client.beta.chat.completions.runTools({
* model: "c1/openai/gpt-5/v-20250930",
* messages: [...],
* tools,
* });
*/
import { z } from "zod";
import zodToJsonSchema from "zod-to-json-schema";
// ============================================================================
// Web Search Tool
// ============================================================================
export const webSearchSchema = z.object({
query: z.string().min(1).describe("The search query"),
max_results: z
.number()
.int()
.min(1)
.max(10)
.default(5)
.describe("Maximum number of results to return (1-10)"),
include_answer: z
.boolean()
.default(true)
.describe("Include AI-generated answer summary"),
});
export type WebSearchArgs = z.infer<typeof webSearchSchema>;
export const webSearchTool = {
type: "function" as const,
function: {
name: "web_search",
description:
"Search the web for current information using a search API. Use this for recent events, news, or information that may have changed recently.",
parameters: zodToJsonSchema(webSearchSchema),
},
};
// ============================================================================
// Product/Inventory Tools
// ============================================================================
export const productLookupSchema = z.object({
product_type: z
.enum(["gloves", "hat", "scarf", "all"])
.optional()
.describe("Type of product to lookup, or 'all' for entire inventory"),
filter: z
.object({
min_price: z.number().optional(),
max_price: z.number().optional(),
in_stock_only: z.boolean().default(true),
})
.optional()
.describe("Optional filters for product search"),
});
export type ProductLookupArgs = z.infer<typeof productLookupSchema>;
export const productLookupTool = {
type: "function" as const,
function: {
name: "lookup_product",
description:
"Look up products in the inventory database. Returns product details including price, availability, and specifications.",
parameters: zodToJsonSchema(productLookupSchema),
},
};
// ============================================================================
// Order Creation Tool
// ============================================================================
const orderItemSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("gloves"),
size: z.enum(["XS", "S", "M", "L", "XL", "XXL"]),
color: z.string().min(1),
quantity: z.number().int().min(1).max(100),
}),
z.object({
type: z.literal("hat"),
style: z.enum(["beanie", "baseball", "fedora", "bucket"]),
color: z.string().min(1),
quantity: z.number().int().min(1).max(100),
}),
z.object({
type: z.literal("scarf"),
length: z.enum(["short", "medium", "long"]),
material: z.enum(["wool", "cotton", "silk", "cashmere"]),
quantity: z.number().int().min(1).max(100),
}),
]);
export const createOrderSchema = z.object({
customer_email: z
.string()
.email()
.describe("Customer's email address for order confirmation"),
items: z
.array(orderItemSchema)
.min(1)
.max(20)
.describe("Array of items to include in the order (max 20)"),
shipping_address: z.object({
street: z.string().min(1),
city: z.string().min(1),
state: z.string().length(2), // US state code
zip: z.string().regex(/^\d{5}(-\d{4})?$/), // ZIP or ZIP+4
country: z.string().default("US"),
}),
notes: z.string().optional().describe("Optional order notes or instructions"),
});
export type CreateOrderArgs = z.infer<typeof createOrderSchema>;
export type OrderItem = z.infer<typeof orderItemSchema>;
export const createOrderTool = {
type: "function" as const,
function: {
name: "create_order",
description:
"Create a new product order with customer information, items, and shipping address. Returns order ID and confirmation details.",
parameters: zodToJsonSchema(createOrderSchema),
},
};
// ============================================================================
// Database Query Tool
// ============================================================================
export const databaseQuerySchema = z.object({
query_type: z
.enum(["select", "aggregate", "search"])
.describe("Type of database query to perform"),
table: z
.string()
.describe("Database table name (e.g., 'users', 'products', 'orders')"),
filters: z
.record(z.any())
.optional()
.describe("Filter conditions as key-value pairs"),
limit: z.number().int().min(1).max(100).default(20).describe("Result limit"),
});
export type DatabaseQueryArgs = z.infer<typeof databaseQuerySchema>;
export const databaseQueryTool = {
type: "function" as const,
function: {
name: "query_database",
description:
"Query the database for information. Supports select, aggregate, and search operations on various tables.",
parameters: zodToJsonSchema(databaseQuerySchema),
},
};
// ============================================================================
// Data Visualization Tool
// ============================================================================
export const createVisualizationSchema = z.object({
chart_type: z
.enum(["bar", "line", "pie", "scatter", "area"])
.describe("Type of chart to create"),
data: z
.array(
z.object({
label: z.string(),
value: z.number(),
})
)
.min(1)
.describe("Data points for the visualization"),
title: z.string().min(1).describe("Chart title"),
x_label: z.string().optional().describe("X-axis label"),
y_label: z.string().optional().describe("Y-axis label"),
});
export type CreateVisualizationArgs = z.infer<typeof createVisualizationSchema>;
export const createVisualizationTool = {
type: "function" as const,
function: {
name: "create_visualization",
description:
"Create a data visualization chart. Returns chart configuration that will be rendered in the UI.",
parameters: zodToJsonSchema(createVisualizationSchema),
},
};
// ============================================================================
// Email Tool
// ============================================================================
export const sendEmailSchema = z.object({
to: z.string().email().describe("Recipient email address"),
subject: z.string().min(1).max(200).describe("Email subject line"),
body: z.string().min(1).describe("Email body content (supports HTML)"),
cc: z.array(z.string().email()).optional().describe("CC recipients"),
bcc: z.array(z.string().email()).optional().describe("BCC recipients"),
});
export type SendEmailArgs = z.infer<typeof sendEmailSchema>;
export const sendEmailTool = {
type: "function" as const,
function: {
name: "send_email",
description:
"Send an email to one or more recipients. Use this to send notifications, confirmations, or responses to customers.",
parameters: zodToJsonSchema(sendEmailSchema),
},
};
// ============================================================================
// Calendar/Scheduling Tool
// ============================================================================
export const scheduleEventSchema = z.object({
title: z.string().min(1).describe("Event title"),
start_time: z.string().datetime().describe("Event start time (ISO 8601)"),
end_time: z.string().datetime().describe("Event end time (ISO 8601)"),
description: z.string().optional().describe("Event description"),
attendees: z
.array(z.string().email())
.optional()
.describe("List of attendee email addresses"),
location: z.string().optional().describe("Event location or meeting link"),
reminder_minutes: z
.number()
.int()
.min(0)
.default(15)
.describe("Minutes before event to send reminder"),
});
export type ScheduleEventArgs = z.infer<typeof scheduleEventSchema>;
export const scheduleEventTool = {
type: "function" as const,
function: {
name: "schedule_event",
description:
"Schedule a calendar event with attendees, location, and reminders.",
parameters: zodToJsonSchema(scheduleEventSchema),
},
};
// ============================================================================
// File Upload Tool
// ============================================================================
export const uploadFileSchema = z.object({
file_name: z.string().min(1).describe("Name of the file"),
file_type: z
.string()
.describe("MIME type (e.g., 'image/png', 'application/pdf')"),
file_size: z.number().int().min(1).describe("File size in bytes"),
description: z.string().optional().describe("File description or metadata"),
});
export type UploadFileArgs = z.infer<typeof uploadFileSchema>;
export const uploadFileTool = {
type: "function" as const,
function: {
name: "upload_file",
description:
"Upload a file to cloud storage. Returns storage URL and file metadata.",
parameters: zodToJsonSchema(uploadFileSchema),
},
};
// ============================================================================
// Export All Tools
// ============================================================================
export const allTools = [
webSearchTool,
productLookupTool,
createOrderTool,
databaseQueryTool,
createVisualizationTool,
sendEmailTool,
scheduleEventTool,
uploadFileTool,
];
/**
* Helper to get tools by category
*/
export function getToolsByCategory(category: "ecommerce" | "data" | "communication" | "all") {
const categories = {
ecommerce: [productLookupTool, createOrderTool],
data: [databaseQueryTool, createVisualizationTool],
communication: [sendEmailTool, scheduleEventTool],
all: allTools,
};
return categories[category];
}
/**
* Validation helper
*/
export function validateToolArgs<T extends z.ZodType>(
schema: T,
args: unknown
): z.infer<T> {
return schema.parse(args);
}