diff --git a/.github/workflows/firebase-hosting-deploy.yml b/.github/workflows/firebase-hosting-deploy.yml index 170729d..1520183 100644 --- a/.github/workflows/firebase-hosting-deploy.yml +++ b/.github/workflows/firebase-hosting-deploy.yml @@ -38,7 +38,10 @@ jobs: VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }} run: bun run build + # A pull request only builds: with channelId live it would publish its + # own branch to the production site, then fail on the read-only token. - name: Deploy to Firebase + if: github.event_name == 'push' uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/README.md b/README.md index 071c44a..9c41cf6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ bun run preview ```bash # Generate content metadata from @falai/agent package -bun run generate:metadata +bun run metadata # Run type checking bun run typecheck @@ -99,7 +99,7 @@ falai-site/ ## 🔄 How It Works -1. **Metadata Generation**: The `generate-content-metadata.ts` script scans the `@falai/agent` package for documentation and examples +1. **Metadata Generation**: The `scripts/metadata.ts` script scans the `@falai/agent` package for documentation and examples 2. **Static Content Copy**: Vite plugin copies markdown and code files to the build output 3. **Dynamic Routing**: React Router creates routes for all docs and examples based on metadata 4. **Runtime Fetching**: Content is fetched at runtime and rendered with syntax highlighting @@ -124,7 +124,7 @@ The documentation content is automatically sourced from the `@falai/agent` packa 1. Update the `@falai/agent` package version in `package.json` 2. Run `bun install` -3. Run `bun run generate:metadata` to update metadata +3. Run `bun run metadata` to update metadata 4. Build or start dev server ## 🚢 Deployment diff --git a/bun.lock b/bun.lock index c8c4e90..d3dc4c2 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "falai-site", "dependencies": { - "@falai/agent": "^3.4.4", + "@falai/agent": "^4.0.0", "react": "^19.2.8", "react-dom": "^19.2.8", "react-markdown": "^10.1.0", @@ -142,7 +142,7 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@falai/agent": ["@falai/agent@3.4.4", "", { "dependencies": { "@providerkit/core": "^0.10.0", "loglevel": "^1.9.2" }, "peerDependencies": { "@opensearch-project/opensearch": "^2.0.0", "@prisma/client": "^6.0.0", "better-sqlite3": "^11.0.0 || ^12.0.0", "ioredis": "^5.0.0", "mongodb": "^6.0.0", "pg": "^8.0.0" }, "optionalPeers": ["@opensearch-project/opensearch", "@prisma/client", "better-sqlite3", "ioredis", "mongodb", "pg"] }, "sha512-vEzSsXblWU6cnzEdR6TngaSVYGJ+smzdVtY3n+98vr4XmQff6MqyC3RTipHsmI+61B7C5+5J7gFnb2VxOJwzXg=="], + "@falai/agent": ["@falai/agent@4.0.0", "", { "dependencies": { "@providerkit/core": "^0.20.0", "loglevel": "^1.9.2" }, "peerDependencies": { "@opensearch-project/opensearch": "^2.0.0", "@prisma/client": "^6.0.0", "better-sqlite3": "^11.0.0 || ^12.0.0", "ioredis": "^5.0.0", "mongodb": "^6.0.0", "pg": "^8.0.0" }, "optionalPeers": ["@opensearch-project/opensearch", "@prisma/client", "better-sqlite3", "ioredis", "mongodb", "pg"] }, "sha512-HM2NzrPBn2IjvWRS1bujM7IvPJx0NW4vcJ5sVPDwMvw0Ky6YsAvxqT3MSkZ/F0wpbbWYehS/91Fxc04DK4mjeA=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -162,7 +162,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@providerkit/core": ["@providerkit/core@0.10.0", "", { "peerDependencies": { "zod": "^4" }, "optionalPeers": ["zod"] }, "sha512-GaS9G97yia5l0GJx00utaf/UFtaLmpEp5iv43VawaY2o+xyv8BvJj/sRsilpm52KVR9dcbyDFivRnJ1z9emqSw=="], + "@providerkit/core": ["@providerkit/core@0.20.0", "", { "peerDependencies": { "zod": "^4" }, "optionalPeers": ["zod"] }, "sha512-tiQEzCjvwxy673fpDoE1kA10pnhU/O9bSPN9k5pX33nCXrPRDd1rUh51ufYzA8e/lLMNAfC9yoKdEMuT8BZr0A=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], diff --git a/index.html b/index.html index b83678e..5f5ce95 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ @@ -30,7 +30,7 @@ diff --git a/package.json b/package.json index 8b3f9f3..790ae18 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "deploy": "bun run build && firebase deploy --only hosting" }, "dependencies": { - "@falai/agent": "^3.4.4", + "@falai/agent": "^4.0.0", "react": "^19.2.8", "react-dom": "^19.2.8", "react-markdown": "^10.1.0", diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 1f5f455..6a8a89d 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -2,52 +2,60 @@ import { Link } from "react-router-dom"; import { LazyCodeBlock } from "../components/CodeBlock.lazy"; import { startHereRoute } from "../lib/content"; -const QUICKSTART = `import { Agent, OpenAIProvider } from "@falai/agent"; +const INSTALL = `bun add @falai/agent`; -const agent = new Agent({ - name: "Booking Bot", - provider: new OpenAIProvider({ - apiKey: process.env.OPENAI_API_KEY, - model: "gpt-4o-mini", - }), +const FIRST_CALL = `const r = await agent.turn({ sessionId: "demo", message: "hi" }); +console.log(r.messages[0]?.text); // something like "Hi! What should I call you?"`; + +const QUICKSTART = `import { falai, GeminiProvider } from "@falai/agent"; + +const f = falai().fields({ + name: { type: "string", ask: "Ask for the person's name. Do not sound like a form." }, }); -agent.route("book") - .collect({ - hotel: { type: "string" }, - guests: { type: "number" }, - date: { type: "string", format: "date" }, - }) - .then(async ({ data }) => { - return \`Booked \${data.hotel} for \${data.guests} on \${data.date}.\`; - }); +const agent = f.agent({ + name: "Ana", + provider: new GeminiProvider({ apiKey: process.env.GEMINI_API_KEY ?? "", model: "gemini-2.5-flash" }), + flows: [ + f.flow({ + id: "welcome", + name: "Welcome", + on: [{ message: [] }], + steps: [ + { id: "name", collect: ["name"] }, + { id: "help", prompt: "Thank them by name and ask how you can help." }, + ], + }), + ], +}); -await agent.send("Book Grand Hotel for 2 next Friday");`; +const r = await agent.turn({ sessionId: "demo", message: "hi" }); +console.log(r.messages[0]?.text);`; const FEATURES = [ { - title: "Schema-driven extraction", - body: "Define data contracts once. The model fills them — predictably, with validation built in.", + title: "Flows with steps", + body: "A flow is a trigger plus an ordered list of steps. A step talks, sends fixed text, runs your code, waits or branches.", }, { - title: "Routes you can reason about", - body: "Conversations move through typed steps. Skip what's already known, run only what's missing.", + title: "Fields declared once", + body: "Say what each piece of data is and how to ask for it. The user can answer in any order, and a step is skipped when its data is already known.", }, { - title: "Pluggable providers", - body: "OpenAI, Anthropic, Gemini, OpenRouter. Swap models without rewriting your flows.", + title: "At most two model calls per turn", + body: "One call understands the message. One call writes the reply. Every result reports llmCalls, so you can test the budget.", }, { - title: "Tools with structure", - body: "Tool calls run through your code with metadata, validation, and lifecycle hooks.", + title: "One call, everything to do", + body: "agent.turn() takes a message, a wake-up, an event or a start. It returns the messages to send and the wake-ups to schedule. It never sends, sleeps or saves.", }, { - title: "Optional persistence", - body: "Sessions survive restarts. Bring your DB or use a built-in adapter.", + title: "You own the storage", + body: "A Store has load and save. Memory, Postgres, Prisma, Redis, Mongo, SQLite and OpenSearch stores come with the package.", }, { - title: "TypeScript-first", - body: "Generic Agent with full inference. No untyped escape hatches.", + title: "Flows as JSON", + body: "A FlowSpec is a flow stored as JSON. fromSpec turns it into a flow, so a flow written in an editor or a chat is the same object as one written in TypeScript.", }, ]; @@ -62,8 +70,8 @@ export function HomePage() { that act like code.

- A conversational state engine where the model understands and your code stays in - control. Schema-driven, predictable, production-ready. + Define flows, steps and tools in TypeScript. The AI is called only to understand what + the customer wrote and to write the reply. Your code decides the rest.

@@ -72,6 +80,9 @@ export function HomePage() { Browse docs + + Coming from 3.x? +

Quick start

-

One agent, one route, one extraction. That's the whole thing.

+

Install the package, then run one turn. The agent behind that call is below.

- + + +

Why @falai/agent

- Existing solutions are either too unpredictable to ship or too heavy to use. We - picked the middle. + Version 4 has one model: a flow starts when something happens, then runs its steps.

    @@ -111,7 +123,7 @@ export function HomePage() {

    Ready to build?

    -

    The getting-started guide takes about ten minutes.

    +

    The tutorial builds one agent in five short pages.

    Read the guide