chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,90 @@
import { describe, it, expect, vi } from "vitest";
import {
generateA2uiImpl,
buildA2uiOperationsFromToolCall,
RENDER_A2UI_TOOL_SCHEMA,
CUSTOM_CATALOG_ID,
} from "../generate-a2ui";
describe("generateA2uiImpl", () => {
it("returns system prompt from context entries", () => {
const result = generateA2uiImpl({
messages: [],
contextEntries: [
{ value: "Component catalog info" },
{ value: "More context" },
],
});
expect(result.systemPrompt).toContain("Component catalog info");
expect(result.systemPrompt).toContain("More context");
});
it("filters empty/missing context values", () => {
const result = generateA2uiImpl({
messages: [],
contextEntries: [{ value: "" }, { noValue: true }, { value: "keep" }],
});
expect(result.systemPrompt).toBe("keep");
});
it("returns tool schema and choice", () => {
const result = generateA2uiImpl({ messages: [] });
expect(result.toolSchema.name).toBe("render_a2ui");
expect(result.toolChoice).toBe("render_a2ui");
});
it("passes messages through", () => {
const msgs = [{ role: "user", content: "hello" }];
const result = generateA2uiImpl({ messages: msgs });
expect(result.messages).toBe(msgs);
});
it("returns the default catalog ID", () => {
const result = generateA2uiImpl({ messages: [] });
expect(result.catalogId).toBe(CUSTOM_CATALOG_ID);
});
});
describe("buildA2uiOperationsFromToolCall", () => {
it("builds create_surface + update_components", () => {
const result = buildA2uiOperationsFromToolCall({
surfaceId: "s1",
catalogId: "cat1",
components: [{ id: "root", component: "Title" }],
});
expect(result.a2ui_operations).toHaveLength(2);
expect(result.a2ui_operations[0].type).toBe("create_surface");
expect(result.a2ui_operations[1].type).toBe("update_components");
});
it("includes update_data_model when data provided", () => {
const result = buildA2uiOperationsFromToolCall({
surfaceId: "s1",
catalogId: "cat1",
components: [{ id: "root" }],
data: { key: "value" },
});
expect(result.a2ui_operations).toHaveLength(3);
expect(result.a2ui_operations[2].type).toBe("update_data_model");
});
it("defaults surfaceId and catalogId", () => {
const result = buildA2uiOperationsFromToolCall({ components: [] });
expect(result.a2ui_operations[0].surfaceId).toBe("dynamic-surface");
expect(result.a2ui_operations[0].catalogId).toBe(CUSTOM_CATALOG_ID);
});
it("warns on empty components", () => {
const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
buildA2uiOperationsFromToolCall({
surfaceId: "s1",
catalogId: "c1",
components: [],
});
expect(spy).toHaveBeenCalledWith(
expect.stringContaining("empty components"),
"s1",
);
spy.mockRestore();
});
});
@@ -0,0 +1,79 @@
import { describe, it, expect } from "vitest";
import { getWeatherImpl } from "../get-weather";
describe("getWeatherImpl", () => {
it("returns all required fields", () => {
const result = getWeatherImpl("Tokyo");
expect(result).toHaveProperty("city");
expect(result).toHaveProperty("temperature");
expect(result).toHaveProperty("humidity");
expect(result).toHaveProperty("wind_speed");
expect(result).toHaveProperty("feels_like");
expect(result).toHaveProperty("conditions");
});
it("passes city name through", () => {
expect(getWeatherImpl("San Francisco").city).toBe("San Francisco");
});
it("is deterministic for the same city", () => {
const r1 = getWeatherImpl("Tokyo");
const r2 = getWeatherImpl("Tokyo");
expect(r1.temperature).toBe(r2.temperature);
expect(r1.conditions).toBe(r2.conditions);
});
it("produces different results for different cities", () => {
const r1 = getWeatherImpl("Tokyo");
const r2 = getWeatherImpl("London");
expect(r1).not.toEqual(r2);
});
it("temperature is within expected range (20-95)", () => {
const result = getWeatherImpl("Berlin");
expect(result.temperature).toBeGreaterThanOrEqual(20);
expect(result.temperature).toBeLessThanOrEqual(95);
});
it("humidity is within expected range (30-90)", () => {
const result = getWeatherImpl("Paris");
expect(result.humidity).toBeGreaterThanOrEqual(30);
expect(result.humidity).toBeLessThanOrEqual(90);
});
it("wind_speed is within expected range (2-30)", () => {
const result = getWeatherImpl("Sydney");
expect(result.wind_speed).toBeGreaterThanOrEqual(2);
expect(result.wind_speed).toBeLessThanOrEqual(30);
});
it("conditions is one of the known values", () => {
const known = [
"Sunny",
"Partly Cloudy",
"Cloudy",
"Overcast",
"Light Rain",
"Heavy Rain",
"Thunderstorm",
"Snow",
"Foggy",
"Windy",
];
const result = getWeatherImpl("Miami");
expect(known).toContain(result.conditions);
});
it("is case-insensitive for seed (lowercased internally)", () => {
const r1 = getWeatherImpl("tokyo");
const r2 = getWeatherImpl("TOKYO");
expect(r1.temperature).toBe(r2.temperature);
});
it("feels_like is within ±5 of temperature", () => {
const result = getWeatherImpl("Berlin");
expect(
Math.abs(result.feels_like - result.temperature),
).toBeLessThanOrEqual(5);
});
});
@@ -0,0 +1,61 @@
import { describe, it, expect } from "vitest";
import { queryDataImpl } from "../query-data";
describe("queryDataImpl", () => {
it("returns an array", () => {
expect(Array.isArray(queryDataImpl("test"))).toBe(true);
});
it("returns 66 rows (11 categories x 6 months)", () => {
expect(queryDataImpl("test")).toHaveLength(66);
});
it("rows have expected columns", () => {
const row = queryDataImpl("test")[0];
expect(row).toHaveProperty("date");
expect(row).toHaveProperty("category");
expect(row).toHaveProperty("subcategory");
expect(row).toHaveProperty("amount");
expect(row).toHaveProperty("type");
expect(row).toHaveProperty("notes");
});
it("returns same data regardless of query", () => {
const r1 = queryDataImpl("revenue");
const r2 = queryDataImpl("expenses");
expect(r1).toEqual(r2);
});
it("is deterministic (seeded RNG)", () => {
const r1 = queryDataImpl("test");
const r2 = queryDataImpl("test");
expect(r1).toEqual(r2);
});
it("categories are Revenue or Expenses", () => {
const rows = queryDataImpl("test");
const categories = new Set(rows.map((r) => r.category));
expect(categories).toEqual(new Set(["Revenue", "Expenses"]));
});
it("types are income or expense", () => {
const rows = queryDataImpl("test");
const types = new Set(rows.map((r) => r.type));
expect(types).toEqual(new Set(["income", "expense"]));
});
it("dates are in 2026", () => {
const rows = queryDataImpl("test");
for (const row of rows) {
expect(row.date).toMatch(/^2026-/);
}
});
it("amount is a numeric string", () => {
const rows = queryDataImpl("test");
for (const row of rows) {
expect(Number(row.amount)).not.toBeNaN();
expect(Number(row.amount)).toBeGreaterThan(0);
}
});
});
@@ -0,0 +1,124 @@
import { describe, it, expect } from "vitest";
import {
manageSalesTodosImpl,
getSalesTodosImpl,
INITIAL_SALES_TODOS,
} from "../sales-todos";
describe("INITIAL_SALES_TODOS", () => {
it("has 3 items", () => {
expect(INITIAL_SALES_TODOS).toHaveLength(3);
});
it("has fixed IDs", () => {
expect(INITIAL_SALES_TODOS[0].id).toBe("st-001");
expect(INITIAL_SALES_TODOS[1].id).toBe("st-002");
expect(INITIAL_SALES_TODOS[2].id).toBe("st-003");
});
});
describe("manageSalesTodosImpl", () => {
it("assigns UUID to todos missing ID", () => {
const result = manageSalesTodosImpl([{ title: "New deal" }]);
expect(result[0].id).toBeTruthy();
expect(result[0].id.length).toBeGreaterThan(0);
});
it("preserves existing IDs", () => {
const result = manageSalesTodosImpl([{ id: "keep-me", title: "Deal" }]);
expect(result[0].id).toBe("keep-me");
});
it("provides defaults for missing fields", () => {
const result = manageSalesTodosImpl([{ title: "Minimal" }]);
expect(result[0].stage).toBe("prospect");
expect(result[0].value).toBe(0);
expect(result[0].completed).toBe(false);
expect(result[0].dueDate).toBe("");
expect(result[0].assignee).toBe("");
});
it("preserves provided fields", () => {
const result = manageSalesTodosImpl([
{
id: "x",
title: "Big Deal",
stage: "negotiation",
value: 50000,
dueDate: "2026-05-01",
assignee: "Alice",
completed: true,
},
]);
expect(result[0].title).toBe("Big Deal");
expect(result[0].stage).toBe("negotiation");
expect(result[0].value).toBe(50000);
expect(result[0].completed).toBe(true);
});
it("handles empty array", () => {
const result = manageSalesTodosImpl([]);
expect(result).toEqual([]);
});
it("handles multiple todos", () => {
const result = manageSalesTodosImpl([
{ title: "A" },
{ title: "B" },
{ title: "C" },
]);
expect(result).toHaveLength(3);
// Each should get a unique ID
const ids = new Set(result.map((r) => r.id));
expect(ids.size).toBe(3);
});
it("replaces empty string id with a generated UUID", () => {
const result = manageSalesTodosImpl([{ id: "", title: "x" }]);
expect(result[0].id).toBeTruthy();
expect(result[0].id).not.toBe("");
expect(result[0].id.length).toBeGreaterThan(0);
});
});
describe("getSalesTodosImpl", () => {
it("returns initial todos when undefined", () => {
const result = getSalesTodosImpl(undefined);
expect(result).toHaveLength(3);
expect(result[0].id).toBe("st-001");
});
it("returns initial todos when null", () => {
const result = getSalesTodosImpl(null);
expect(result).toHaveLength(3);
});
it("returns empty array when given empty array", () => {
const result = getSalesTodosImpl([]);
// empty array means user cleared all todos — return empty, not defaults
expect(result).toHaveLength(0);
});
it("returns provided todos when non-empty", () => {
const todos = [
{
id: "1",
title: "Test",
stage: "prospect" as const,
value: 100,
dueDate: "",
assignee: "",
completed: false,
},
];
const result = getSalesTodosImpl(todos);
expect(result).toHaveLength(1);
expect(result[0].title).toBe("Test");
});
it("returns a copy, not the original INITIAL_SALES_TODOS reference", () => {
const result = getSalesTodosImpl(undefined);
expect(result).not.toBe(INITIAL_SALES_TODOS);
expect(result).toEqual(INITIAL_SALES_TODOS);
});
});
@@ -0,0 +1,26 @@
import { describe, it, expect } from "vitest";
import { scheduleMeetingImpl } from "../schedule-meeting";
describe("scheduleMeetingImpl", () => {
it("returns pending_approval status", () => {
expect(scheduleMeetingImpl("discuss roadmap").status).toBe(
"pending_approval",
);
});
it("includes the reason", () => {
expect(scheduleMeetingImpl("quarterly review").reason).toBe(
"quarterly review",
);
});
it("uses default 30-minute duration", () => {
expect(scheduleMeetingImpl("sync").duration_minutes).toBe(30);
});
it("accepts custom duration", () => {
expect(scheduleMeetingImpl("deep dive", 60).duration_minutes).toBe(60);
});
it("includes a message", () => {
const result = scheduleMeetingImpl("onboarding", 45);
expect(result.message).toContain("onboarding");
expect(result.message).toContain("45");
});
});
@@ -0,0 +1,52 @@
import { describe, it, expect } from "vitest";
import { searchFlightsImpl } from "../search-flights";
import type { Flight } from "../types";
describe("searchFlightsImpl", () => {
const mockFlight: Flight = {
airline: "Test Air",
airlineLogo: "https://example.com/logo.png",
flightNumber: "TA100",
origin: "SFO",
destination: "JFK",
date: "Tue, Apr 15",
departureTime: "08:00",
arrivalTime: "16:00",
duration: "5h",
status: "On Time",
statusColor: "#22c55e",
price: "$299",
currency: "USD",
};
it("returns flights and schema", () => {
const result = searchFlightsImpl([mockFlight]);
expect(result).toHaveProperty("flights");
expect(result).toHaveProperty("schema");
});
it("passes flights through unchanged", () => {
const result = searchFlightsImpl([mockFlight]);
expect(result.flights).toHaveLength(1);
expect(result.flights[0]).toEqual(mockFlight);
});
it("returns empty schema object", () => {
const result = searchFlightsImpl([mockFlight]);
expect(result.schema).toEqual({});
});
it("handles empty flights array", () => {
const result = searchFlightsImpl([]);
expect(result.flights).toHaveLength(0);
});
it("handles multiple flights", () => {
const result = searchFlightsImpl([
mockFlight,
{ ...mockFlight, flightNumber: "TA200" },
]);
expect(result.flights).toHaveLength(2);
expect(result.flights[1].flightNumber).toBe("TA200");
});
});
@@ -0,0 +1,98 @@
/**
* Dynamic A2UI generation via secondary LLM call.
* TypeScript equivalent of showcase/shared/python/tools/generate_a2ui.py.
*/
export const CUSTOM_CATALOG_ID = "copilotkit://app-dashboard-catalog";
export const DESIGN_A2UI_SURFACE_TOOL_SCHEMA = {
name: "_design_a2ui_surface",
description: "Render a dynamic A2UI v0.9 surface.",
parameters: {
type: "object",
properties: {
surfaceId: { type: "string", description: "Unique surface identifier." },
catalogId: { type: "string", description: "The catalog ID." },
components: {
type: "array",
items: { type: "object" },
description: "A2UI v0.9 component array (flat format).",
},
data: {
type: "object",
description: "Optional initial data model for the surface.",
},
},
required: ["surfaceId", "catalogId", "components"],
},
} as const;
export interface GenerateA2UIInput {
messages: Array<Record<string, unknown>>;
contextEntries?: Array<Record<string, unknown>>;
}
export interface GenerateA2UIResult {
systemPrompt: string;
toolSchema: typeof DESIGN_A2UI_SURFACE_TOOL_SCHEMA;
toolChoice: string;
messages: Array<Record<string, unknown>>;
catalogId: string;
}
export function generateA2uiImpl(input: GenerateA2UIInput): GenerateA2UIResult {
const contextText = (input.contextEntries ?? [])
.filter(
(e): e is Record<string, unknown> & { value: string } =>
typeof e === "object" &&
typeof e.value === "string" &&
e.value.length > 0,
)
.map((e) => e.value)
.join("\n\n");
return {
systemPrompt: contextText,
toolSchema: DESIGN_A2UI_SURFACE_TOOL_SCHEMA,
toolChoice: "_design_a2ui_surface",
messages: input.messages,
catalogId: CUSTOM_CATALOG_ID,
};
}
export interface A2UIOperation {
type: "create_surface" | "update_components" | "update_data_model";
surfaceId: string;
catalogId?: string;
components?: Array<Record<string, unknown>>;
data?: Record<string, unknown>;
}
export function buildA2uiOperationsFromToolCall(
args: Record<string, unknown>,
): {
a2ui_operations: A2UIOperation[];
} {
const surfaceId = (args.surfaceId as string) ?? "dynamic-surface";
const catalogId = (args.catalogId as string) ?? CUSTOM_CATALOG_ID;
const components = (args.components as Array<Record<string, unknown>>) ?? [];
const data = args.data as Record<string, unknown> | undefined;
if (components.length === 0) {
console.warn(
"buildA2uiOperationsFromToolCall: empty components for surface",
surfaceId,
);
}
const ops: A2UIOperation[] = [
{ type: "create_surface", surfaceId, catalogId },
{ type: "update_components", surfaceId, components },
];
if (data) {
ops.push({ type: "update_data_model", surfaceId, data });
}
return { a2ui_operations: ops };
}
@@ -0,0 +1,67 @@
/**
* Mock weather data tool implementation.
*
* TypeScript equivalent of showcase/shared/python/tools/get_weather.py.
* Uses a simple seed derived from the city name so repeated calls for
* the same city return consistent results within a session.
*/
import { WeatherResult } from "./types";
const CONDITIONS = [
"Sunny",
"Partly Cloudy",
"Cloudy",
"Overcast",
"Light Rain",
"Heavy Rain",
"Thunderstorm",
"Snow",
"Foggy",
"Windy",
];
/**
* Simple seeded pseudo-random number generator (mulberry32).
* Produces deterministic output for a given seed so the same city
* always returns the same weather within a process lifetime.
*/
function seededRandom(seed: number): () => number {
let t = seed;
return () => {
t = (t + 0x6d2b79f5) | 0;
let v = Math.imul(t ^ (t >>> 15), 1 | t);
v = (v + Math.imul(v ^ (v >>> 7), 61 | v)) ^ v;
return ((v ^ (v >>> 14)) >>> 0) / 4294967296;
};
}
function hashString(s: string): number {
let hash = 0;
for (let i = 0; i < s.length; i++) {
hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0;
}
return hash;
}
function randInt(rng: () => number, min: number, max: number): number {
return Math.floor(rng() * (max - min + 1)) + min;
}
function randChoice<T>(rng: () => number, arr: T[]): T {
return arr[Math.floor(rng() * arr.length)];
}
export function getWeatherImpl(city: string): WeatherResult {
const rng = seededRandom(hashString(city.toLowerCase()));
const temperature = randInt(rng, 20, 95);
return {
city,
temperature,
humidity: randInt(rng, 30, 90),
wind_speed: randInt(rng, 2, 30),
feels_like: temperature + randInt(rng, -5, 5),
conditions: randChoice(rng, CONDITIONS),
};
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Shared TypeScript tool implementations for CopilotKit showcase.
*
* Pure functions with no framework imports — consumed by
* langgraph-typescript, mastra, claude-sdk-typescript, and any
* future TypeScript-based showcase packages.
*/
export { getWeatherImpl } from "./get-weather";
export { queryDataImpl } from "./query-data";
export type { DataRow } from "./query-data";
export {
manageSalesTodosImpl,
getSalesTodosImpl,
INITIAL_SALES_TODOS,
} from "./sales-todos";
export { searchFlightsImpl } from "./search-flights";
export { scheduleMeetingImpl } from "./schedule-meeting";
export type { ScheduleMeetingResult } from "./schedule-meeting";
export {
generateA2uiImpl,
buildA2uiOperationsFromToolCall,
RENDER_A2UI_TOOL_SCHEMA,
CUSTOM_CATALOG_ID,
} from "./generate-a2ui";
export type {
GenerateA2UIInput,
GenerateA2UIResult,
A2UIOperation,
} from "./generate-a2ui";
export type { SalesTodo, SalesStage, Flight, WeatherResult } from "./types";
@@ -0,0 +1,107 @@
/**
* Query data tool implementation — returns mock financial data.
*
* TypeScript equivalent of showcase/shared/python/tools/query_data.py.
* In the future this could read a CSV, but for TS backend simplicity
* we use generated mock data matching the Python fallback format.
*/
export interface DataRow {
date: string;
category: string;
subcategory: string;
amount: string;
type: string;
notes: string;
}
// Seeded random for deterministic mock data
function seededRandom(seed: number): () => number {
let s = seed;
return () => {
s = (s * 1103515245 + 12345) & 0x7fffffff;
return s / 0x7fffffff;
};
}
function generateMockData(): DataRow[] {
const rand = seededRandom(42);
const categories: Array<{
category: string;
subcategory: string;
type: string;
}> = [
{
category: "Revenue",
subcategory: "Enterprise Subscriptions",
type: "income",
},
{ category: "Revenue", subcategory: "Pro Tier Upgrades", type: "income" },
{ category: "Revenue", subcategory: "API Usage Overages", type: "income" },
{ category: "Revenue", subcategory: "Consulting Services", type: "income" },
{ category: "Revenue", subcategory: "Marketplace Sales", type: "income" },
{
category: "Expenses",
subcategory: "Engineering Salaries",
type: "expense",
},
{ category: "Expenses", subcategory: "Product Team", type: "expense" },
{
category: "Expenses",
subcategory: "AWS Infrastructure",
type: "expense",
},
{ category: "Expenses", subcategory: "Marketing", type: "expense" },
{ category: "Expenses", subcategory: "Customer Success", type: "expense" },
{ category: "Expenses", subcategory: "AI Model Costs", type: "expense" },
];
const notes: Record<string, string> = {
"Enterprise Subscriptions": "3 new enterprise customers",
"Pro Tier Upgrades": "31 upgrades + reduced churn",
"API Usage Overages": "Heavy usage from top-10 accounts",
"Consulting Services": "2 implementation projects",
"Marketplace Sales": "Partner integrations revenue",
"Engineering Salaries": "7 engineers + 2 contractors",
"Product Team": "PM + designers + QA",
"AWS Infrastructure": "Compute + storage + bandwidth",
Marketing: "Paid ads + content + events",
"Customer Success": "3 CSMs + tooling",
"AI Model Costs": "OpenAI + Anthropic API spend",
};
const rows: DataRow[] = [];
const months = ["01", "02", "03", "04", "05", "06"];
for (const month of months) {
for (const cat of categories) {
const baseAmount =
cat.type === "income"
? 15000 + Math.floor(rand() * 35000)
: 8000 + Math.floor(rand() * 40000);
const day = String(1 + Math.floor(rand() * 28)).padStart(2, "0");
rows.push({
date: `2026-${month}-${day}`,
category: cat.category,
subcategory: cat.subcategory,
amount: String(baseAmount),
type: cat.type,
notes: notes[cat.subcategory] ?? "",
});
}
}
return rows;
}
const MOCK_DATA: DataRow[] = generateMockData();
/**
* Query the database. Takes natural language.
*
* Always call before showing a chart or graph. Returns the full
* dataset as a list of row objects.
*/
export function queryDataImpl(_query: string): DataRow[] {
return MOCK_DATA;
}
@@ -0,0 +1,65 @@
/**
* Sales todos tool implementation.
*
* TypeScript equivalent of showcase/shared/python/tools/sales_todos.py.
*/
import { SalesTodo } from "./types";
export const INITIAL_SALES_TODOS: SalesTodo[] = [
{
id: "st-001",
title: "Follow up with Acme Corp on enterprise proposal",
stage: "proposal",
value: 85000,
dueDate: "2026-04-15",
assignee: "Sarah Chen",
completed: false,
},
{
id: "st-002",
title: "Qualify lead from TechFlow demo request",
stage: "prospect",
value: 42000,
dueDate: "2026-04-18",
assignee: "Mike Johnson",
completed: false,
},
{
id: "st-003",
title: "Send contract to DataViz Inc for final review",
stage: "negotiation",
value: 120000,
dueDate: "2026-04-20",
assignee: "Sarah Chen",
completed: false,
},
];
/**
* Assign crypto.randomUUID() to any todos missing an ID, then return
* the updated list.
*/
export function manageSalesTodosImpl(todos: Partial<SalesTodo>[]): SalesTodo[] {
return todos.map((todo) => ({
id: todo.id || crypto.randomUUID(),
title: todo.title ?? "",
stage: todo.stage ?? "prospect",
value: todo.value ?? 0,
dueDate: todo.dueDate ?? "",
assignee: todo.assignee ?? "",
completed: todo.completed ?? false,
}));
}
/**
* Return current todos or initial defaults if none provided.
*/
export function getSalesTodosImpl(
currentTodos?: Partial<SalesTodo>[] | null,
): SalesTodo[] {
if (currentTodos != null) {
return currentTodos.length > 0 ? manageSalesTodosImpl(currentTodos) : [];
}
return [...INITIAL_SALES_TODOS];
}
@@ -0,0 +1,24 @@
/**
* Schedule meeting tool implementation — HITL gated.
*
* TypeScript equivalent of showcase/shared/python/tools/schedule_meeting.py.
*/
export interface ScheduleMeetingResult {
status: "pending_approval";
reason: string;
duration_minutes: number;
message: string;
}
export function scheduleMeetingImpl(
reason: string,
durationMinutes: number = 30,
): ScheduleMeetingResult {
return {
status: "pending_approval",
reason,
duration_minutes: durationMinutes,
message: `Meeting request: ${reason} (${durationMinutes} min). Awaiting user time selection.`,
};
}
@@ -0,0 +1,19 @@
/**
* Search flights tool implementation.
*
* Simple passthrough that wraps flights for AG-UI rendering.
* The frontend GenUI components handle presentation.
*/
import { Flight } from "./types";
/**
* Wrap the provided flights array for consumption by the frontend
* flight-search GenUI component.
*/
export function searchFlightsImpl(flights: Flight[]): {
flights: Flight[];
schema: Record<string, unknown>;
} {
return { flights, schema: {} };
}
+49
View File
@@ -0,0 +1,49 @@
/**
* Shared type definitions for showcase tools.
*
* These mirror the Python types in showcase/shared/python/tools/types.py
* and the frontend types in showcase/shared/frontend/src/types.ts.
*/
export type SalesStage =
| "prospect"
| "qualified"
| "proposal"
| "negotiation"
| "closed-won"
| "closed-lost";
export interface SalesTodo {
id: string;
title: string;
stage: SalesStage;
value: number;
dueDate: string;
assignee: string;
completed: boolean;
}
export interface Flight {
airline: string;
airlineLogo: string;
flightNumber: string;
origin: string;
destination: string;
date: string;
departureTime: string;
arrivalTime: string;
duration: string;
status: string;
statusColor: string;
price: string;
currency: string;
}
export interface WeatherResult {
city: string;
temperature: number;
humidity: number;
wind_speed: number;
feels_like: number;
conditions: string;
}
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
root: "showcase/shared/typescript",
include: ["tools/__tests__/**/*.test.ts"],
},
});