diff --git a/.changeset/sharp-messages-always.md b/.changeset/sharp-messages-always.md new file mode 100644 index 0000000..31172bf --- /dev/null +++ b/.changeset/sharp-messages-always.md @@ -0,0 +1,9 @@ +--- +"@statelyai/agent": minor +--- + +Add first-class session messages and deterministic always transitions. + +Agent states and snapshots now carry `messages` alongside `context`. State hooks receive messages, transition results can replace messages, and helper functions are exported for appending user, assistant, and system messages. + +Machines can now define `always` transitions for deterministic eventless routing. Runtime sessions journal these transitions as internal events so persistence and restore remain replayable. diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index ddc9555..51fcd0d 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -6,7 +6,7 @@ runs: - uses: pnpm/action-setup@v2 - uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.18.0 - name: install pnpm run: npm i pnpm@latest -g diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57c57f8..b6f87ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: setup node.js uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 22.18.0 - name: install pnpm run: npm i pnpm@latest -g - name: setup pnpm config diff --git a/.gitignore b/.gitignore index 323729b..a52881a 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,5 @@ dist/ .pnp.* .vscode/settings.json + +docs/superpowers diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..91d5f6f --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.18.0 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..91d5f6f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.18.0 diff --git a/.vscode/launch.json b/.vscode/launch.json index d6e2a9a..87a02fb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,8 +19,8 @@ "name": "Debug Current File", "program": "${file}", "cwd": "${workspaceFolder}", - "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ts-node", - "runtimeArgs": ["--transpile-only"], + "runtimeExecutable": "node", + "runtimeArgs": ["--import", "tsx"], "outFiles": ["${workspaceFolder}/dist/**/*.js"], "sourceMaps": true, "smartStep": true, diff --git a/docs/crewai-parity.md b/docs/crewai-parity.md new file mode 100644 index 0000000..3514e24 --- /dev/null +++ b/docs/crewai-parity.md @@ -0,0 +1,59 @@ +# CrewAI Flows Parity + +## Scope + +This document tracks where `@statelyai/agent` covers the practical workflow patterns shown in the official `crewAIInc/crewAI-examples` Flows directory as of April 26, 2026. + +It is intentionally scoped to: + +- runnable workflow patterns +- state/routing/runtime behavior +- human-in-the-loop and iteration behavior +- examples and tests in this repo + +It is intentionally not scoped to: + +- CrewAI-specific decorators and class APIs +- CrewAI Enterprise triggers/integrations as products +- Python-only configuration formats + +## External reference + +CrewAI’s official examples repo currently lists these Flow examples: + +- Content Creator Flow +- Email Auto Responder Flow +- Lead Score Flow +- Meeting Assistant Flow +- Self Evaluation Loop Flow +- Write a Book with Flows + +Primary sources: + +- [CrewAI examples index](https://docs.crewai.com/en/examples/example) +- [CrewAI Flows docs](https://docs.crewai.com/en/concepts/flows) +- [CrewAI examples repo](https://github.com/crewAIInc/crewAI-examples) + +## Matrix + + + +| CrewAI Flow example | Status | Agent equivalent | +| --- | --- | --- | +| Content Creator Flow | Covered | [`examples/content-creator-flow.ts`](/Users/davidkpiano/Code/agent/examples/content-creator-flow.ts), [`src/crewai-equivalents/content-creator-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/content-creator-flow.test.ts) | +| Email Auto Responder Flow | Covered | [`examples/email-auto-responder-flow.ts`](/Users/davidkpiano/Code/agent/examples/email-auto-responder-flow.ts), [`src/crewai-equivalents/email-auto-responder-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/email-auto-responder-flow.test.ts) | +| Lead Score Flow | Covered | [`examples/lead-score-flow.ts`](/Users/davidkpiano/Code/agent/examples/lead-score-flow.ts), [`src/crewai-equivalents/lead-score-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/lead-score-flow.test.ts) | +| Meeting Assistant Flow | Covered | [`examples/meeting-assistant-flow.ts`](/Users/davidkpiano/Code/agent/examples/meeting-assistant-flow.ts), [`src/crewai-equivalents/meeting-assistant-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/meeting-assistant-flow.test.ts) | +| Self Evaluation Loop Flow | Covered | [`examples/self-evaluation-loop-flow.ts`](/Users/davidkpiano/Code/agent/examples/self-evaluation-loop-flow.ts), [`src/crewai-equivalents/self-evaluation-loop-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/self-evaluation-loop-flow.test.ts) | +| Write a Book with Flows | Covered | [`examples/write-a-book-flow.ts`](/Users/davidkpiano/Code/agent/examples/write-a-book-flow.ts), [`src/crewai-equivalents/write-a-book-flow.test.ts`](/Users/davidkpiano/Code/agent/src/crewai-equivalents/write-a-book-flow.test.ts) | + +## Notes + +- CrewAI’s `content_creator_flow/` directory in the current examples repo clone is empty, so that equivalence is based on the current official descriptions: multi-format content routing across blog, LinkedIn, and research outputs. +- Several of these patterns overlap with existing generic examples here, but they are still represented as CrewAI-named examples so the parity surface is explicit instead of inferred. + +## Differences + +- Logic remains explicit state-machine logic instead of CrewAI decorator-based method routing. +- Durable sessions are modeled through first-class snapshots and event journals rather than framework-managed persistence hidden behind class methods. +- Fan-out is expressed in plain JavaScript `Promise.all(...)` inside invokes where that is simpler than introducing framework-specific branching primitives. diff --git a/docs/langgraph-parity.md b/docs/langgraph-parity.md new file mode 100644 index 0000000..00f04f1 --- /dev/null +++ b/docs/langgraph-parity.md @@ -0,0 +1,80 @@ +# LangGraphJS Parity + +## Scope + +This document tracks where `@statelyai/agent` currently matches the practical end result of `langchain-ai/langgraphjs` for core workflow/runtime behavior. + +It is intentionally scoped to: + +- core orchestration concepts +- durable session behavior +- streaming/runtime transport behavior +- runnable examples and tests in this repo + +It is intentionally not scoped to: + +- LangGraph Platform deployment features +- LangGraph Studio +- LangGraph UI / framework SDK packages +- checkpoint backend packages as separate published adapters + +## External reference + +As of April 25, 2026, the upstream `langgraphjs` repo exposes: + +- core packages under [`libs/`](https://github.com/langchain-ai/langgraphjs/tree/main/libs), including `langgraph`, `langgraph-core`, checkpoint packages, supervisor/swarm helpers, SDKs, and UI packages +- runnable examples under [`examples/`](https://github.com/langchain-ai/langgraphjs/tree/main/examples), including quickstart, plan-and-execute, reflection, rewoo, SQL agent, multi-agent, chatbots, RAG, and UI transport examples + +The parity target here is the core graph/runtime layer, not the whole surrounding product/package ecosystem. + +## Matrix + + + +| LangGraphJS concept | Status | Agent equivalent | +| --- | --- | --- | +| Branching / conditional routing | Covered | [`examples/branching.ts`](/Users/davidkpiano/Code/agent/examples/branching.ts), [`src/langgraph-equivalents/branching.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/branching.test.ts) | +| Subgraphs / nested flows | Covered | [`examples/subflow.ts`](/Users/davidkpiano/Code/agent/examples/subflow.ts), [`examples/conditional-subflow.ts`](/Users/davidkpiano/Code/agent/examples/conditional-subflow.ts), [`src/langgraph-equivalents/subflow.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/subflow.test.ts), [`src/langgraph-equivalents/conditional-subflow.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/conditional-subflow.test.ts) | +| Human-in-the-loop / approval gate | Covered | [`examples/hitl.ts`](/Users/davidkpiano/Code/agent/examples/hitl.ts), [`src/langgraph-equivalents/hitl.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/hitl.test.ts) | +| Durable sessions / restore from snapshots + events | Covered | [`examples/persistence.ts`](/Users/davidkpiano/Code/agent/examples/persistence.ts), [`src/langgraph-equivalents/persistence.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/persistence.test.ts) | +| Streaming emitted parts | Covered | [`examples/persistent-streaming.ts`](/Users/davidkpiano/Code/agent/examples/persistent-streaming.ts), [`src/langgraph-equivalents/streaming.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/streaming.test.ts), [`src/langgraph-equivalents/persistent-streaming.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/persistent-streaming.test.ts) | +| Tool calling with intermediate progress | Covered | [`examples/tool-calling.ts`](/Users/davidkpiano/Code/agent/examples/tool-calling.ts), [`src/langgraph-equivalents/tool-calling.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/tool-calling.test.ts) | +| Retry loops / explicit recovery | Covered | [`examples/error-retry.ts`](/Users/davidkpiano/Code/agent/examples/error-retry.ts), [`src/langgraph-equivalents/error-retry.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/error-retry.test.ts) | +| Plan-and-execute | Covered | [`examples/plan-and-execute.ts`](/Users/davidkpiano/Code/agent/examples/plan-and-execute.ts), [`src/langgraph-equivalents/plan-and-execute.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/plan-and-execute.test.ts) | +| Map-reduce style workflows | Covered | [`examples/map-reduce.ts`](/Users/davidkpiano/Code/agent/examples/map-reduce.ts), [`src/langgraph-equivalents/map-reduce.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/map-reduce.test.ts) | +| Reflection loop | Covered | [`examples/reflection.ts`](/Users/davidkpiano/Code/agent/examples/reflection.ts), [`src/langgraph-equivalents/reflection.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/reflection.test.ts) | +| ReWOO-style planner / worker decomposition | Covered | [`examples/rewoo.ts`](/Users/davidkpiano/Code/agent/examples/rewoo.ts), [`src/langgraph-equivalents/rewoo.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/rewoo.test.ts) | +| Supervisor routing | Covered | [`examples/supervisor.ts`](/Users/davidkpiano/Code/agent/examples/supervisor.ts), [`examples/persistent-supervisor.ts`](/Users/davidkpiano/Code/agent/examples/persistent-supervisor.ts), [`src/langgraph-equivalents/supervisor.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/supervisor.test.ts), [`src/langgraph-equivalents/persistent-supervisor.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/persistent-supervisor.test.ts) | +| Multi-agent handoffs | Covered | [`examples/multi-agent-network.ts`](/Users/davidkpiano/Code/agent/examples/multi-agent-network.ts), [`examples/persistent-multi-agent-network.ts`](/Users/davidkpiano/Code/agent/examples/persistent-multi-agent-network.ts), [`src/langgraph-equivalents/multi-agent-network.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/multi-agent-network.test.ts), [`src/langgraph-equivalents/persistent-multi-agent-network.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/persistent-multi-agent-network.test.ts) | +| SQL/tool-heavy agent workflow | Covered | [`examples/sql-agent.ts`](/Users/davidkpiano/Code/agent/examples/sql-agent.ts), [`src/langgraph-equivalents/sql-agent.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/sql-agent.test.ts) | +| ReAct-style agent | Covered | [`examples/react-agent-from-scratch.ts`](/Users/davidkpiano/Code/agent/examples/react-agent-from-scratch.ts), [`examples/react-agent.ts`](/Users/davidkpiano/Code/agent/examples/react-agent.ts), [`src/langgraph-equivalents/prebuilt-react.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/prebuilt-react.test.ts) | +| Message-centric chatbot state | Covered | [`examples/chatbot-messages.ts`](/Users/davidkpiano/Code/agent/examples/chatbot-messages.ts), [`src/langgraph-equivalents/chatbot-messages.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/chatbot-messages.test.ts) | +| Retrieval-augmented generation | Covered | [`examples/rag.ts`](/Users/davidkpiano/Code/agent/examples/rag.ts), [`src/langgraph-equivalents/rag.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/rag.test.ts) | +| HTTP session transport | Covered | [`examples/http-session.ts`](/Users/davidkpiano/Code/agent/examples/http-session.ts), [`src/examples.test.ts`](/Users/davidkpiano/Code/agent/src/examples.test.ts) | +| Durable HTTP streaming transport / reconnect | Covered | [`examples/http-streaming-session.ts`](/Users/davidkpiano/Code/agent/examples/http-streaming-session.ts), [`src/examples.test.ts`](/Users/davidkpiano/Code/agent/src/examples.test.ts) | +| Graph export / visualization support | Covered | [`src/graph/index.ts`](/Users/davidkpiano/Code/agent/src/graph/index.ts), [`src/xstate/index.ts`](/Users/davidkpiano/Code/agent/src/xstate/index.ts), [`src/langgraph-equivalents/graph.test.ts`](/Users/davidkpiano/Code/agent/src/langgraph-equivalents/graph.test.ts) | + +## Intentional differences + +These are currently deliberate, not gaps: + +- Logic stays pure: `(state, event) -> { nextState, effects }`. +- Emitted events are live runtime effects, not durable journal entries. +- Durable behavior is based on first-class snapshot + event persistence rather than in-memory graph execution with optional add-ons. +- `run.on(...)` is reserved for emitted events only; terminal/runtime hooks use dedicated methods like `run.onDone(...)`. +- Parallelism is expected to be expressed in plain JavaScript where possible, rather than forcing a dedicated graph primitive when `Promise.all(...)` is enough. + +## Still missing or intentionally out of scope + +These are the main areas not yet covered by a first-class parity example: + +- swarm-specific helper APIs comparable to `libs/langgraph-swarm` +- published checkpoint backends as separate installable packages +- UI framework transport examples comparable to `examples/ui-react`, `examples/ui-svelte`, etc. +- platform-only features such as threads, cron jobs, Studio, and deployment APIs + +## Recommended next wave + +1. Decide whether swarm/supervisor helper packages should exist as additive libraries or remain plain examples. +2. Decide whether storage adapters should stay example-level or become installable packages. +3. Only after that, consider UI transport helpers if package surface matters beyond examples. diff --git a/docs/superpowers/specs/2026-04-08-langgraph-core-replacement-design.md b/docs/superpowers/specs/2026-04-08-langgraph-core-replacement-design.md new file mode 100644 index 0000000..a304dee --- /dev/null +++ b/docs/superpowers/specs/2026-04-08-langgraph-core-replacement-design.md @@ -0,0 +1,676 @@ +# LangGraph Core Replacement Design + +## Goal + +Evolve `agent` into a LangGraph-core replacement in terms of runtime behavior and developer outcomes, while improving on LangGraph through a simpler, more explicit state-machine model. + +The target is semantic parity for core orchestration use cases, not API compatibility. Developers should be able to build the same classes of systems in `agent` that they can build with LangGraph core, but using `agent`'s state-machine API and philosophy. + +## Core Philosophies + +The design is constrained by these principles: + +1. Logic is pure. + The semantic center remains: + + ```ts + (currentState, event) => { + return { nextState, effects }; + } + ``` + + State transition logic should stay deterministic, replayable, and inspectable. + +2. Effect execution is first-class. + The runtime must make it easy to both transition state and execute effects, but without collapsing transition logic into effectful code. Effects are driven by the machine, not hidden as the machine. + +3. Durability is core. + `agent` should treat persisted state and event history as first-class runtime concerns, not as optional add-ons. + +4. Runner-agnostic execution. + The runtime must be able to run anywhere: Node, Vercel, Cloudflare, Durable Objects, workers, and other environments. Storage and execution coordination must be abstracted behind portable interfaces. + +5. Improve on LangGraph rather than imitate it. + Do not copy LangGraph's graph-builder surface area or its more complex runtime semantics where a simpler state-machine formulation produces the same outcome. + +## Scope + +In scope: + +- core orchestration behavior currently covered by `@langchain/langgraph` +- runtime behavior tests and runnable examples from LangGraph core, rewritten as `agent`-idiomatic equivalents +- persistence, replay, resume, streaming, pending states, submachine composition, and high-value prebuilt agent patterns + +Out of scope for this design: + +- LangGraph monorepo packages outside core +- API/CLI/server packages +- UI framework SDKs and app templates +- type-level compatibility with LangGraph +- exact API or import-path matching + +## Design Summary + +`agent` should become a durable run engine for state machines. + +A machine definition remains declarative and mostly pure: + +- states +- transitions +- invoke/effect boundaries +- final outputs + +A run becomes the primary runtime object: + +- backed by an append-only replay journal +- accelerated by persisted snapshots +- observable through a first-class event stream +- resumable from persisted state +- portable across runners via abstract persistence and scheduling interfaces + +This yields a model where the semantics are simple: + +- transitions are deterministic +- invokes are explicit effect boundaries +- external and internal machine events drive progress +- streaming is run-level, not bolted on +- persistence is a core contract + +## Runtime Model + +The machine model should stay state-machine-first rather than graph-builder-first. + +### Machine Definition + +A machine definition remains responsible for: + +- context initialization +- current state value +- transition handlers +- invoke definitions +- terminal outputs + +The machine should continue to express workflows such as: + +- branching +- tool-using agents +- human review loops +- multi-step planning and execution +- nested machine orchestration + +### Run Model + +Introduce a durable session as the central execution concept. + +`sessionId` should be the canonical persisted identifier. + +`run` can still be a useful public term for the live handle returned by the runtime, but the durable identity should align with actor/session terminology. + +Each run has: + +- `sessionId` +- `machineId` +- input payload +- current snapshot +- append-only replay journal +- status +- subscribers + +Suggested shape: + +```ts +interface AgentRun { + sessionId: string; + status: "active" | "pending" | "done" | "error"; + getSnapshot(): AgentSnapshot; + send(event: { type: string; [key: string]: unknown }): Promise; + on(type: string, handler: (event: unknown) => void): () => void; +} +``` + +An async-iterator surface is still useful, but it is additive. The emitter-style `on(...)` API is the required phase-1 contract. + +`on(...)` is a live listener only. It should not be treated as a history or replay API. Historical actor events belong to the journal/store layer. + +### Durable Execution Boundaries + +Phase 1 durability should exist at machine boundaries, not inside arbitrary user async code. + +Persist the replayable machine events: + +- external events sent to the actor +- internal machine events emitted by the runtime +- invoke completion events +- invoke failure events + +Do not claim sub-invoke durability for plain `Promise.all(...)` or arbitrary nested promises. + +### Pending and Human-in-the-Loop + +Do not introduce an interrupt primitive as a core concept. + +Use explicit pending states and external events: + +```ts +review: { + on: { + approve: { target: "send" }, + reject: { target: "revise" }, + }, +} +``` + +This preserves: + +- deterministic replay +- explicit control flow +- durable resume semantics +- runner portability + +### Submachine Composition + +Do not introduce graph/subgraph composition as a first-class structural primitive in phase 1. + +Instead, allow composition through normal execution: + +```ts +writing: { + invoke: async ({ context }) => { + return executeAgentMachine(writerMachine, { + input: { + topic: context.topic, + research: context.research, + }, + }); + }, +} +``` + +This is sufficient for most LangGraph subgraph outcomes without graph-specific composition APIs. + +## Purity and Effects + +The central architectural requirement is preserving pure transition logic while still making effects first-class. + +Conceptually, every runtime step should be explainable as: + +```ts +const { nextState, effects } = transition(currentState, event); +``` + +Where: + +- `nextState` is deterministic +- `effects` are explicit runtime work to perform next + +In practice, current `agent` APIs already combine these concerns inside state configs. The design should move the runtime toward an explicit internal split even if the external authoring API remains ergonomic. + +That means: + +- transition logic should remain replayable without rerunning effects +- effect lifecycle should be represented through emitted machine events +- invoke results should be fed back as events, not hidden mutations + +This should follow the same philosophy as XState invoke completion: + +- invoke completion becomes an internal done event +- invoke failure becomes an internal error event +- the machine progresses by consuming events, not by direct mutation from effect code + +This is the main improvement opportunity over LangGraph's more graph-runtime-centric model. + +## Persistence Model + +The canonical persisted representation is an append-only replay journal. + +Snapshots are derived state used to accelerate replay and resume. + +### Replay Journal + +The replay journal is the source of truth. It contains the actual events consumed by the actor, including synthetic internal events produced by the runtime. + +Suggested minimal replayable event family: + +```ts +type JournalEvent = + | { type: "xstate.init"; input?: unknown; at: number } + | { type: "user.message"; [key: string]: unknown; at: number } + | { type: "approve"; at: number } + | { type: "xstate.done.invoke.research"; output: unknown; at: number } + | { + type: "xstate.error.invoke.research"; + error: SerializedError; + at: number; + }; +``` + +The exact event naming can be refined, but the important property is that invoke done/error are actor events, not metadata records. + +### Runtime and Audit Events + +Derived runtime records can still exist for observability and subscriptions, but they are not the canonical replay source. + +Examples: + +- state entered +- transition applied +- snapshot persisted +- session completed +- session failed + +These belong in the runtime event stream and diagnostics layer. + +### Snapshots + +Suggested snapshot shape: + +```ts +type AgentSnapshot = { + value: string; + context: Record; + status: "active" | "done" | "error" | "pending"; + createdAt: number; + sessionId: string; + input: Record>; + output?: unknown; + error?: SerializedError; +}; + +type PersistedSnapshot = { + sessionId: string; + snapshot: AgentSnapshot; + afterSequence: number; + createdAt: number; +}; +``` + +This aligns the live snapshot shape closely with XState snapshots: + +- `value` +- `context` +- `status` + +with additional metadata such as: + +- `createdAt` +- `sessionId` +- optional `output` +- optional `error` + +The `afterSequence` field identifies the last replayable journal event already reflected in the snapshot, so replay can resume from a known journal offset without inventing a separate semantic version. + +### Replay Model + +Restore a run by: + +1. loading the latest snapshot +2. replaying all journal events after that snapshot +3. reconstructing the current live run state + +If no snapshot exists, replay from `xstate.init`. + +### Storage Interface + +Persistence must be abstracted behind a portable interface: + +```ts +interface RunStore { + append(sessionId: string, event: JournalEvent): Promise; + loadEvents(sessionId: string, afterSequence?: number): Promise; + loadLatestSnapshot(sessionId: string): Promise; + saveSnapshot(snapshot: PersistedSnapshot): Promise; +} +``` + +This is what makes the runtime portable to: + +- in-memory test stores +- SQL or key-value stores +- Cloudflare Durable Objects +- Vercel-backed durable layers +- custom app infrastructure + +### Important Phase 1 Constraint + +Invoke internals are opaque unless user code or future helpers explicitly expose finer-grained durable progress. + +This means: + +- plain async code remains ergonomic +- invoke-level durability is honest +- future `task(...)` or `parallel(...)` helpers remain additive + +## Streaming Model + +Streaming must be a first-class capability of a run. + +Separate: + +1. durable runtime events +2. ephemeral stream parts + +### Run-Level Events + +Suggested public stream model: + +```ts +type RunEmitterEvent = + | { type: "state"; snapshot: AgentSnapshot } + | { type: "machine.event"; event: JournalEvent } + | { type: "runtime"; event: RuntimeEvent } + | { type: "part"; part: StreamPart } + | { type: "done"; output: unknown } + | { type: "error"; error: unknown }; +``` + +Where `machine.event` refers to replayable actor events and `runtime` refers to derived lifecycle records useful for debugging and orchestration. + +These event shapes describe what a live run may emit. They do not imply that late subscribers receive replayed history through `on(...)`. + +Suggested runtime event family: + +```ts +type RuntimeEvent = + | { type: "session.started"; sessionId: string; at: number } + | { type: "session.restored"; sessionId: string; afterSequence: number; at: number } + | { type: "snapshot.persisted"; sessionId: string; afterSequence: number; at: number } + | { type: "session.completed"; sessionId: string; at: number } + | { type: "session.failed"; sessionId: string; error: SerializedError; at: number }; +``` + +Derived events such as `state.entered` and `transition.applied` are still useful for richer inspection, but they are not required for this phase. + +### Stream Parts + +For common model/tool streaming shapes, align with Vercel AI SDK-style part conventions where practical: + +```ts +type StreamPart = + | { type: "text-start"; id: string } + | { type: "text-delta"; id: string; delta: string } + | { type: "text-end"; id: string } + | { type: "tool-input-start"; toolCallId: string; toolName: string } + | { type: "tool-input-delta"; toolCallId: string; inputTextDelta: string } + | { type: "tool-input-available"; toolCallId: string; toolName: string; input: unknown } + | { type: "tool-output-available"; toolCallId: string; output: unknown } + | { type: "reasoning-part"; text: string } + | { type: "data"; data: unknown } + | { type: "error"; errorText: string }; +``` + +Provide convenience listeners on top: + +```ts +run.on("textPart", ({ delta }) => {}); +run.on("toolCall", ({ toolCallId, toolName, input }) => {}); +run.on("toolResult", ({ toolCallId, output }) => {}); +``` + +### Emission Model + +Invoke code should be able to emit live parts using a separate enqueue/emission argument: + +```ts +drafting: { + invoke: async ({ context }, enq) => { + for await (const chunk of streamText(...)) { + enq.emit({ type: "text-delta", id: "draft", delta: chunk }); + } + return { draft: finalText }; + }, +} +``` + +Durable runtime events are persisted. Stream parts are ephemeral by default in phase 1. + +Using a second argument is important because it preserves a useful authoring distinction: + +- one-argument functions are easier to lint as pure/no-emission +- two-argument functions explicitly opt into streaming side effects + +### Emitted Schemas + +Machine definitions should support emitted event schemas alongside input and external event schemas. + +Suggested direction: + +```ts +schemas: { + input: ..., + events: { + approve: ..., + reject: ..., + }, + emitted: { + textPart: ..., + toolCall: ..., + toolResult: ..., + }, +} +``` + +This gives: + +- typed live emissions +- runtime validation of emitted parts +- stronger UI integration +- symmetry with event schemas + +## Runner-Agnostic Architecture + +The runtime must not assume: + +- long-lived Node processes +- a specific queue system +- a specific database +- process-local memory as truth + +The core should be split into: + +1. pure machine semantics +2. durable run orchestration +3. storage abstraction +4. environment-specific runner adapters + +This makes it possible to showcase: + +- standard Node process usage +- Vercel usage +- Cloudflare Worker usage +- Cloudflare Durable Object usage + +Durable Objects are especially relevant because they demonstrate the design clearly: + +- replay journal and snapshot persistence can live in DO state +- run coordination can be serialized naturally +- stream subscriptions can be implemented via the object lifecycle + +The important point is that Durable Objects should be an example adapter, not the core assumption. + +## Capability Mapping from LangGraph Core + +### Directly Mappable + +- graph orchestration -> explicit machine states and transitions +- shared state update workflows -> `invoke` + `onDone` context updates +- human-in-the-loop -> pending states + external events +- subgraphs/subflows -> nested machine execution +- streaming -> run-level event emitter + stream parts + emitted schemas +- persistence/resume -> event journal + snapshots +- prebuilt agent patterns -> curated machine factories + +### Needs Reinterpretation + +- reducers/channels -> avoid first-class graph-channel runtime semantics in phase 1 +- graph builder APIs -> do not mirror +- `START` / `END` constants -> unnecessary as authoring primitives +- explicit interrupt primitive -> defer + +### Deferred + +- graph-level true concurrent branch semantics with reducer joins +- durable sub-invoke task boundaries +- remote/API client compatibility +- type-level compatibility tests + +## LangGraph Test Port Strategy + +Only port: + +- runtime behavior tests +- runnable examples + +Do not port: + +- type-only tests +- API surface compatibility tests + +### Priority Test Groups + +1. Graph/state behavior + - `graph.test.ts` + - `errors.test.ts` + - `constants.test.ts` + +2. Execution/runtime behavior + - selected `pregel.test.ts` + - `pregel.read.test.ts` + - `pregel/stream.test.ts` + - `execution_info.test.ts` + +3. Persistence and replay + - `python_port/checkpoint.test.ts` + - `remote-graph-resumable.test.ts` + +4. Prebuilt agent behavior + - `prebuilt.test.ts` + - `prebuilt.int.test.ts` + +5. Runtime schema behavior + - relevant portions of `zod_state.test.ts` + +Each imported test should become an `agent`-idiomatic equivalent that asserts the same end-result behavior through the state-machine runtime. + +## Example Port Strategy + +Priority LangGraph-equivalent examples to rebuild in `agent`: + +1. quickstart +2. branching +3. wait-user-input / breakpoints +4. persistence +5. subgraph +6. tool-calling +7. create-react-agent / react-agent-from-scratch +8. multi-agent-network +9. plan-and-execute +10. reflection +11. rewoo +12. sql-agent + +Each example should: + +- use `agent`'s machine API +- be runnable locally +- demonstrate the same user outcome +- prefer explicit machine structure over graph-builder mimicry + +## Phased Delivery Plan + +### Phase 0: Lock the Core Contract + +Define: + +- durable run contract +- store interfaces +- restore/replay semantics +- stream event model + +### Phase 1: Durable Runtime + +Build: + +- run object +- journal append/load +- snapshotting +- restoration +- run subscriptions + +### Phase 2: Expressiveness + +Build: + +- better nested machine execution +- pending-state ergonomics +- inspection/trace support +- graph/diagram export + +### Phase 3: Prebuilt Patterns + +Build: + +- ReAct-style machine factory +- tool-calling helpers +- transcript/message helpers + +### Phase 4: Example Corpus + +Rebuild high-value LangGraph examples in `agent`. + +### Phase 5: Behavioral Regression Coverage + +Port and maintain semantic-equivalence tests grouped by capability family. + +## Risks + +1. Conflating transition logic with invoke execution. + This weakens replay semantics and makes portability worse. + +2. Over-promising invoke-level durability. + Plain async code is not automatically resumable at subtask granularity. + +3. Recreating LangGraph builder abstractions instead of improving on them. + This increases complexity without serving the machine-first philosophy. + +4. Mixing durable and ephemeral streams carelessly. + Runtime events and text/tool stream parts need distinct semantics. + +5. Allowing runner assumptions to leak into core. + This would compromise portability across Vercel, Cloudflare, and other environments. + +## Advantages Over LangGraph + +This design improves on LangGraph core in several important ways: + +1. Clearer semantic center. + LangGraph is graph-runtime-first. This design is actor/state-machine-first, so the progression model stays grounded in event consumption and snapshot derivation. + +2. Better purity boundary. + Transition logic remains conceptually pure, while effect execution is explicit and first-class rather than interwoven with graph runtime semantics. + +3. Simpler human-in-the-loop model. + Pending states plus external events are easier to reason about than a dedicated interrupt abstraction for most workflows. + +4. More honest durability. + The replay source is the actor event journal, not a mixed bag of runtime metadata. This makes replay and debugging cleaner. + +5. Better portability. + The runtime is explicitly designed to be runner-agnostic and storage-agnostic, making it a stronger fit for Vercel, Cloudflare Workers, Durable Objects, and other environments. + +6. Easier mental model for composition. + Nested machine execution is ordinary execution, not a special graph/subgraph system. + +7. Better streaming ergonomics. + Run-level subscriptions plus emitted schemas provide a clearer UI/runtime boundary than LangGraph's graph-oriented stream modes. + +## Recommendation + +Proceed with a capability-first expansion of `agent`'s runtime: + +- keep the machine API central +- make durable runs the execution center +- treat event persistence and snapshots as first-class +- make streaming run-level and explicit +- port LangGraph tests/examples as semantic benchmarks + +This produces a cleaner, more durable, and more portable core than LangGraph while still reaching the same practical developer outcomes. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..09f186e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,81 @@ +# Examples + + + +This directory is organized by what a developer is trying to do, not by the underlying primitive. + +## Start Here + +- Building an app route: [`apps/next/`](/Users/davidkpiano/Code/agent/examples/apps/next) or [`apps/cloudflare-agents/`](/Users/davidkpiano/Code/agent/examples/apps/cloudflare-agents) +- Adding durable sessions: [`persistence.ts`](/Users/davidkpiano/Code/agent/examples/persistence.ts) and [`http-session.ts`](/Users/davidkpiano/Code/agent/examples/http-session.ts) +- Streaming text or tool progress: [`next-ai-sdk-ui.ts`](/Users/davidkpiano/Code/agent/examples/next-ai-sdk-ui.ts), [`http-streaming-session.ts`](/Users/davidkpiano/Code/agent/examples/http-streaming-session.ts), and [`tool-calling.ts`](/Users/davidkpiano/Code/agent/examples/tool-calling.ts) +- Studying orchestration patterns: start in `Workflow Examples` + +## App-Shaped Examples + +These are the best starting points when you want code that already looks like a real app: + +- [`apps/next/`](/Users/davidkpiano/Code/agent/examples/apps/next): copy-paste Next.js App Router routes +- [`apps/cloudflare-agents/`](/Users/davidkpiano/Code/agent/examples/apps/cloudflare-agents): copy-paste Cloudflare Agents Worker layout +- [`next-ai-sdk-ui.ts`](/Users/davidkpiano/Code/agent/examples/next-ai-sdk-ui.ts): AI SDK UI route helper +- [`next-app-router.ts`](/Users/davidkpiano/Code/agent/examples/next-app-router.ts): App Router session routes backed by `@statelyai/agent/next` and `@statelyai/agent/http` +- [`cloudflare-agents.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-agents.ts): Node-safe Cloudflare Agents example backed by `@statelyai/agent/cloudflare` + +## Workflow Examples + +These focus on real orchestration patterns: + +- Session-first interactive workflows +- Durable restore and transport patterns +- Multi-step planning, routing, and handoff flows + +- [`persistence.ts`](/Users/davidkpiano/Code/agent/examples/persistence.ts) +- [`persistent-streaming.ts`](/Users/davidkpiano/Code/agent/examples/persistent-streaming.ts) +- [`persistent-supervisor.ts`](/Users/davidkpiano/Code/agent/examples/persistent-supervisor.ts) +- [`persistent-multi-agent-network.ts`](/Users/davidkpiano/Code/agent/examples/persistent-multi-agent-network.ts) +- [`content-creator-flow.ts`](/Users/davidkpiano/Code/agent/examples/content-creator-flow.ts) +- [`email-auto-responder-flow.ts`](/Users/davidkpiano/Code/agent/examples/email-auto-responder-flow.ts) +- [`lead-score-flow.ts`](/Users/davidkpiano/Code/agent/examples/lead-score-flow.ts) +- [`meeting-assistant-flow.ts`](/Users/davidkpiano/Code/agent/examples/meeting-assistant-flow.ts) +- [`self-evaluation-loop-flow.ts`](/Users/davidkpiano/Code/agent/examples/self-evaluation-loop-flow.ts) +- [`spec-agent-loop.ts`](/Users/davidkpiano/Code/agent/examples/spec-agent-loop.ts) +- [`workflow-guardrails.ts`](/Users/davidkpiano/Code/agent/examples/workflow-guardrails.ts) +- [`write-a-book-flow.ts`](/Users/davidkpiano/Code/agent/examples/write-a-book-flow.ts) +- [`plan-and-execute.ts`](/Users/davidkpiano/Code/agent/examples/plan-and-execute.ts) +- [`reflection.ts`](/Users/davidkpiano/Code/agent/examples/reflection.ts) +- [`rewoo.ts`](/Users/davidkpiano/Code/agent/examples/rewoo.ts) +- [`rag.ts`](/Users/davidkpiano/Code/agent/examples/rag.ts) +- [`sql-agent.ts`](/Users/davidkpiano/Code/agent/examples/sql-agent.ts) + +## Runtime / Transport Examples + +- [`http-session.ts`](/Users/davidkpiano/Code/agent/examples/http-session.ts) +- [`http-streaming-session.ts`](/Users/davidkpiano/Code/agent/examples/http-streaming-session.ts) +- [`cloudflare-durable-object.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-durable-object.ts) +- [`cloudflare-durable-network.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-durable-network.ts) + +The reusable pieces behind these examples are exported from `@statelyai/agent/http`, `@statelyai/agent/next`, and `@statelyai/agent/cloudflare`. + +## Reference / Concept Examples + +These are smaller building-block examples: + +- One-shot machine execution: [`simple.ts`](/Users/davidkpiano/Code/agent/examples/simple.ts), [`decide.ts`](/Users/davidkpiano/Code/agent/examples/decide.ts), [`classify.ts`](/Users/davidkpiano/Code/agent/examples/classify.ts) +- Interactive session lifecycle: [`chatbot.ts`](/Users/davidkpiano/Code/agent/examples/chatbot.ts), [`chatbot-messages.ts`](/Users/davidkpiano/Code/agent/examples/chatbot-messages.ts), [`hitl.ts`](/Users/davidkpiano/Code/agent/examples/hitl.ts), [`raffle.ts`](/Users/davidkpiano/Code/agent/examples/raffle.ts) + +- [`simple.ts`](/Users/davidkpiano/Code/agent/examples/simple.ts) +- [`decide.ts`](/Users/davidkpiano/Code/agent/examples/decide.ts) +- [`classify.ts`](/Users/davidkpiano/Code/agent/examples/classify.ts) +- [`adapter.ts`](/Users/davidkpiano/Code/agent/examples/adapter.ts) +- [`tool-calling.ts`](/Users/davidkpiano/Code/agent/examples/tool-calling.ts) +- [`hitl.ts`](/Users/davidkpiano/Code/agent/examples/hitl.ts) +- [`branching.ts`](/Users/davidkpiano/Code/agent/examples/branching.ts) +- [`subflow.ts`](/Users/davidkpiano/Code/agent/examples/subflow.ts) +- [`conditional-subflow.ts`](/Users/davidkpiano/Code/agent/examples/conditional-subflow.ts) + +## Parity Tracking + +- [`../docs/langgraph-parity.md`](/Users/davidkpiano/Code/agent/docs/langgraph-parity.md) +- [`../docs/crewai-parity.md`](/Users/davidkpiano/Code/agent/docs/crewai-parity.md) + +The parity docs track end-result coverage. The files here are the runnable equivalents. diff --git a/examples/_run.ts b/examples/_run.ts new file mode 100644 index 0000000..f632bf0 --- /dev/null +++ b/examples/_run.ts @@ -0,0 +1,242 @@ +import 'dotenv/config'; + +import { generateText, Output } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { createInterface } from 'node:readline/promises'; +import { stdin as input, stdout as output } from 'node:process'; +import { pathToFileURL } from 'node:url'; +import { z } from 'zod'; +import type { + AgentAdapter, + DecideAdapter, + ExecuteResult, + StandardSchemaV1, +} from '../src/index.js'; +export { waitForRunDone, waitForRunSnapshot } from '../src/runtime/index.js'; + +export function isMain(moduleUrl: string): boolean { + const entry = process.argv[1]; + return !!entry && moduleUrl === pathToFileURL(entry).href; +} + +let bufferedLinesPromise: Promise | null = null; +let bufferedLineIndex = 0; + +async function getBufferedLines(): Promise { + if (!bufferedLinesPromise) { + bufferedLinesPromise = (async () => { + const chunks: string[] = []; + + for await (const chunk of input) { + chunks.push(String(chunk)); + } + + return chunks.join('').split(/\r?\n/); + })(); + } + + return bufferedLinesPromise; +} + +export async function prompt(label: string): Promise { + if (!input.isTTY) { + output.write(`${label}: `); + const lines = await getBufferedLines(); + const value = lines[bufferedLineIndex] ?? ''; + bufferedLineIndex += 1; + return value.trim(); + } + + const rl = createInterface({ input, output }); + try { + const value = await rl.question(`${label}: `); + return value.trim(); + } finally { + rl.close(); + } +} + +export function closePrompt(): void { + bufferedLinesPromise = null; + bufferedLineIndex = 0; +} + +export function createExampleModel( + model = 'openai/gpt-5.4-nano' +): Parameters[0]['model'] { + if (!process.env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY is required to run the examples.'); + } + + return openai(resolveOpenAiModel(model)); +} + +export function formatResult(result: ExecuteResult) { + if (result.status === 'done') { + return { + status: result.status, + value: result.state.value, + context: result.context, + messages: result.messages, + output: result.output, + }; + } + + if (result.status === 'pending') { + return { + status: result.status, + value: result.value, + context: result.context, + messages: result.messages, + events: Object.keys(result.events), + }; + } + + return { + status: result.status, + value: result.state.value, + error: result.error, + }; +} + +export function createOpenAiDecisionAdapter(): DecideAdapter { + return { + async decide({ model, prompt, options, reasoning }) { + const optionKeys = Object.keys(options); + + const allSchemaLess = Object.values(options).every((option) => !option.schema); + + if (allSchemaLess && !reasoning) { + const choiceResult = await generateText({ + model: createExampleModel(model), + system: [ + 'Choose exactly one option.', + ...Object.entries(options).map(([key, option]) => `${key}: ${option.description}`), + ].join('\n'), + prompt, + output: Output.choice({ + options: optionKeys, + }), + }); + + return { + choice: choiceResult.output, + data: {} as Record, + }; + } + + const decisionSchemas = optionKeys.map((key) => { + const option = options[key]!; + + return z.object({ + decision: z.literal(key), + data: option.schema ? toZodSchema(option.schema) : z.object({}), + ...(reasoning + ? { reasoning: z.string() } + : {}), + }); + }); + + const decisionSchema = + decisionSchemas.length === 1 + ? decisionSchemas[0]! + : z.union( + decisionSchemas as unknown as [ + z.ZodTypeAny, + z.ZodTypeAny, + ...z.ZodTypeAny[], + ] + ); + + const result = await generateText({ + model: createExampleModel(model), + system: [ + 'Choose exactly one option and return structured output.', + ...Object.entries(options).map(([key, option]) => `${key}: ${option.description}`), + ].join('\n'), + prompt, + output: Output.object({ + schema: decisionSchema, + }), + }); + const output = result.output as { + decision: string; + data: Record; + reasoning?: string; + }; + + return { + choice: output.decision, + data: output.data, + reasoning: output.reasoning, + }; + }, + }; +} + +export function createOpenAiGenerationAdapter(): AgentAdapter { + return { + async generateText({ model, system, prompt, messages, outputSchema }) { + const result = await generateText({ + model: createExampleModel(model), + system, + prompt, + messages: messages as any, + ...(outputSchema + ? { + output: Output.object({ + schema: toZodSchema(outputSchema), + }), + } + : {}), + }); + + const output = result as { output?: unknown; text?: string }; + return output.output ?? output.text ?? result; + }, + }; +} + +export async function generateExampleObject(options: { + schema: StandardSchemaV1; + prompt: string; + system?: string; + model?: string; +}): Promise { + const result = await generateText({ + model: createExampleModel(options.model), + output: Output.object({ + schema: toZodSchema(options.schema), + }), + system: options.system, + prompt: options.prompt, + }); + + return result.output as T; +} + +export async function generateExampleText(options: { + prompt: string; + system?: string; + model?: string; +}): Promise { + const result = await generateText({ + model: createExampleModel(options.model), + system: options.system, + prompt: options.prompt, + }); + + return result.text.trim(); +} + +function resolveOpenAiModel(model: string): string { + return model.startsWith('openai/') ? model.slice('openai/'.length) : model; +} + +function toZodSchema(schema: StandardSchemaV1): z.ZodTypeAny { + if ('_zod' in schema || '_def' in schema) { + return schema as unknown as z.ZodTypeAny; + } + + return z.record(z.string(), z.unknown()); +} diff --git a/examples/adapter.ts b/examples/adapter.ts new file mode 100644 index 0000000..02f368a --- /dev/null +++ b/examples/adapter.ts @@ -0,0 +1,96 @@ +import { z } from 'zod'; +import { + createAgentMachine, + decide, + decideResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + formatResult, + isMain, + prompt, +} from './_run.js'; + +export function createAdapterExample( + adapter: DecideAdapter = createOpenAiDecisionAdapter() +) { + const routeOptions = { + billing: { + description: 'Send the request to billing support.', + schema: z.object({ confidence: z.number().min(0).max(1) }), + }, + general: { + description: 'Handle the request in general support.', + schema: z.object({ confidence: z.number().min(0).max(1) }), + }, + } as const; + + return createAgentMachine({ + id: 'adapter-example', + schemas: { + input: z.object({ message: z.string() }), + output: z.object({ + route: z.string().nullable(), + confidence: z.number().nullable(), + }), + }, + context: (input) => ({ + message: input.message, + route: null as string | null, + confidence: null as number | null, + }), + initial: 'route', + states: { + route: { + schemas: { output: decideResultSchema(routeOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'Route this support request.', + 'Return billing only when the request is clearly about invoices, refunds, or charges.', + 'Otherwise return general.', + '', + context.message, + ].join('\n'), + options: routeOptions, + reasoning: false, + }), + onDone: ({ output }) => { + return { + target: 'done', + context: { + route: output.choice, + confidence: output.data.confidence, + }, + }; + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + route: context.route, + confidence: context.confidence, + }), + }, + }, + }); +} + +async function main() { + try { + const message = await prompt('Message to route'); + const machine = createAdapterExample(); + + console.log(formatResult(await machine.execute(machine.getInitialState({ message })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/ai-sdk.ts b/examples/ai-sdk.ts new file mode 100644 index 0000000..b83b170 --- /dev/null +++ b/examples/ai-sdk.ts @@ -0,0 +1,166 @@ +import { generateText, Output } from 'ai'; +import { z } from 'zod'; +import { + createAgentMachine, + decide, + decideResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { createAiSdkDecisionAdapter } from '../src/ai-sdk/index.js'; +import { + closePrompt, + createExampleModel, + formatResult, + isMain, + prompt, +} from './_run.js'; + +const routeOptions = { + billing: { + description: 'Handle invoices, refunds, subscription charges, and payment issues.', + schema: z.object({ + confidence: z.number().min(0).max(1), + }), + }, + support: { + description: 'Handle product usage questions and troubleshooting requests.', + schema: z.object({ + confidence: z.number().min(0).max(1), + }), + }, +} as const; + +const replySchema = z.object({ + subject: z.string(), + body: z.string(), +}); + +type Route = keyof typeof routeOptions; + +export function createAiSdkExample(options: { + adapter?: DecideAdapter; + draftReply?: (args: { + route: Route; + confidence: number; + message: string; + }) => Promise>; +} = {}) { + const adapter = + options.adapter ?? + createAiSdkDecisionAdapter({ + resolveModel: (model) => createExampleModel(model), + }); + + const draftReply = + options.draftReply ?? + (async ({ + route, + confidence, + message, + }: { + route: Route; + confidence: number; + message: string; + }) => { + const result = await generateText({ + model: createExampleModel('openai/gpt-5.4-nano'), + system: [ + 'Draft a concise support email.', + `Route: ${route}`, + `Classifier confidence: ${confidence.toFixed(2)}`, + 'Return structured output with a subject and body.', + ].join('\n'), + prompt: message, + output: Output.object({ + schema: replySchema, + }), + }); + + return result.output as z.infer; + }); + + return createAgentMachine({ + id: 'ai-sdk-example', + schemas: { + input: z.object({ message: z.string() }), + output: z.object({ + route: z.enum(['billing', 'support']).nullable(), + confidence: z.number().nullable(), + subject: z.string().nullable(), + body: z.string().nullable(), + }), + }, + context: (input) => ({ + message: input.message, + route: null as Route | null, + confidence: null as number | null, + subject: null as string | null, + body: null as string | null, + }), + initial: 'route', + states: { + route: { + schemas: { output: decideResultSchema(routeOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'Route this inbound customer message.', + '', + context.message, + ].join('\n'), + options: routeOptions, + }), + onDone: ({ output }) => ({ + target: 'drafting', + context: { + route: output.choice, + confidence: output.data.confidence, + }, + }), + }, + drafting: { + schemas: { output: replySchema }, + invoke: async ({ context }) => + draftReply({ + route: context.route ?? 'support', + confidence: context.confidence ?? 0, + message: context.message, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { + subject: output.subject, + body: output.body, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + route: context.route, + confidence: context.confidence, + subject: context.subject, + body: context.body, + }), + }, + }, + }); +} + +async function main() { + try { + const message = await prompt('Customer message'); + const machine = createAiSdkExample(); + const result = await machine.execute(machine.getInitialState({ message })); + + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/apps/cloudflare-agents/README.md b/examples/apps/cloudflare-agents/README.md new file mode 100644 index 0000000..ca2cc9c --- /dev/null +++ b/examples/apps/cloudflare-agents/README.md @@ -0,0 +1,10 @@ +# Cloudflare Agents Worker Example + +These files show the Cloudflare Agents integration in a real Worker layout with top-level `agents` imports, instead of the Node-safe lazy import used in [`examples/cloudflare-agents.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-agents.ts). + +Included files: + +- `src/review-workflow-agent.ts`: the Agent class that owns the durable review workflow +- `src/index.ts`: the Worker entrypoint that delegates requests through `routeAgentRequest(...)` + +Use this layout when you want a copy-paste starting point for a real Cloudflare Agents app. diff --git a/examples/apps/cloudflare-agents/src/index.ts b/examples/apps/cloudflare-agents/src/index.ts new file mode 100644 index 0000000..190e940 --- /dev/null +++ b/examples/apps/cloudflare-agents/src/index.ts @@ -0,0 +1,14 @@ +import { routeAgentRequest } from 'agents'; +import { ReviewWorkflowAgent } from './review-workflow-agent.js'; + +export { ReviewWorkflowAgent }; + +export default { + async fetch(request: Request, env: Record) { + return ( + await routeAgentRequest(request, env, { + prefix: '/agents', + }) + ) ?? new Response('Not found', { status: 404 }); + }, +}; diff --git a/examples/apps/cloudflare-agents/src/review-workflow-agent.ts b/examples/apps/cloudflare-agents/src/review-workflow-agent.ts new file mode 100644 index 0000000..d64d72c --- /dev/null +++ b/examples/apps/cloudflare-agents/src/review-workflow-agent.ts @@ -0,0 +1,85 @@ +import { Agent } from 'agents'; +import { restoreSession, startSession, type RunStore } from '../../../../src/index.js'; +import { + createCloudflareAgentRunStore, + type CloudflareAgentRunStoreState, +} from '../../../../src/cloudflare/index.js'; +import { createPersistenceExample } from '../../../persistence.js'; + +export class ReviewWorkflowAgent extends Agent< + Record, + CloudflareAgentRunStoreState +> { + initialState: CloudflareAgentRunStoreState = { + sessions: {}, + }; + + private getStore(): RunStore { + return createCloudflareAgentRunStore({ + getState: () => this.state ?? this.initialState, + setState: (nextState) => this.setState(nextState), + }); + } + + async onRequest(request: Request): Promise { + const url = new URL(request.url); + const machine = createPersistenceExample(); + + if (request.method === 'POST' && url.pathname.endsWith('/start')) { + const body = await request.json() as { request: string }; + const run = await startSession(machine, { + store: this.getStore(), + input: { + request: body.request, + }, + }); + + return Response.json({ + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'POST' && url.pathname.endsWith('/events')) { + const body = await request.json() as { + sessionId: string; + event: { type: 'approve' }; + }; + const run = await restoreSession(machine, { + sessionId: body.sessionId, + store: this.getStore(), + }); + + await run.send(body.event); + + return Response.json({ + sessionId: body.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'GET' && url.pathname.endsWith('/snapshot')) { + const sessionId = requiredSessionId(url); + const run = await restoreSession(machine, { + sessionId, + store: this.getStore(), + }); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + return new Response('Not found', { status: 404 }); + } +} + +function requiredSessionId(url: URL): string { + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId) { + throw new Error('Missing sessionId'); + } + + return sessionId; +} diff --git a/examples/apps/next/README.md b/examples/apps/next/README.md new file mode 100644 index 0000000..bc1ad10 --- /dev/null +++ b/examples/apps/next/README.md @@ -0,0 +1,18 @@ +# Next App Router Examples + +These files show the same `@statelyai/agent` examples in a shape you can drop directly into a Next.js App Router project. + +Included routes: + +- `app/api/chat/route.ts`: AI SDK UI message streaming route +- `app/api/review-sessions/route.ts`: start a durable review session +- `app/api/review-sessions/[sessionId]/route.ts`: fetch a review session snapshot +- `app/api/review-sessions/[sessionId]/events/route.ts`: send events to a review session +- `app/api/stream-sessions/route.ts`: start a streaming session +- `app/api/stream-sessions/[sessionId]/route.ts`: fetch a streaming session snapshot +- `app/api/stream-sessions/[sessionId]/stream/route.ts`: consume the streaming SSE response + +The route handlers are backed by: + +- [`examples/next-app-router.ts`](/Users/davidkpiano/Code/agent/examples/next-app-router.ts) +- [`examples/next-ai-sdk-ui.ts`](/Users/davidkpiano/Code/agent/examples/next-ai-sdk-ui.ts) diff --git a/examples/apps/next/app/api/chat/route.ts b/examples/apps/next/app/api/chat/route.ts new file mode 100644 index 0000000..0cc706e --- /dev/null +++ b/examples/apps/next/app/api/chat/route.ts @@ -0,0 +1,9 @@ +import { + chatRoute, + dynamic, + maxDuration, + runtime, +} from '../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const POST = chatRoute.POST; diff --git a/examples/apps/next/app/api/review-sessions/[sessionId]/events/route.ts b/examples/apps/next/app/api/review-sessions/[sessionId]/events/route.ts new file mode 100644 index 0000000..3234862 --- /dev/null +++ b/examples/apps/next/app/api/review-sessions/[sessionId]/events/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + reviewRoutes, + runtime, +} from '../../../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const POST = reviewRoutes.events.POST; diff --git a/examples/apps/next/app/api/review-sessions/[sessionId]/route.ts b/examples/apps/next/app/api/review-sessions/[sessionId]/route.ts new file mode 100644 index 0000000..0f98e4b --- /dev/null +++ b/examples/apps/next/app/api/review-sessions/[sessionId]/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + reviewRoutes, + runtime, +} from '../../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const GET = reviewRoutes.session.GET; diff --git a/examples/apps/next/app/api/review-sessions/route.ts b/examples/apps/next/app/api/review-sessions/route.ts new file mode 100644 index 0000000..30f3729 --- /dev/null +++ b/examples/apps/next/app/api/review-sessions/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + reviewRoutes, + runtime, +} from '../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const POST = reviewRoutes.sessions.POST; diff --git a/examples/apps/next/app/api/stream-sessions/[sessionId]/route.ts b/examples/apps/next/app/api/stream-sessions/[sessionId]/route.ts new file mode 100644 index 0000000..bce7ee0 --- /dev/null +++ b/examples/apps/next/app/api/stream-sessions/[sessionId]/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + runtime, + streamingRoutes, +} from '../../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const GET = streamingRoutes.session.GET; diff --git a/examples/apps/next/app/api/stream-sessions/[sessionId]/stream/route.ts b/examples/apps/next/app/api/stream-sessions/[sessionId]/stream/route.ts new file mode 100644 index 0000000..89ceefe --- /dev/null +++ b/examples/apps/next/app/api/stream-sessions/[sessionId]/stream/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + runtime, + streamingRoutes, +} from '../../../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const GET = streamingRoutes.stream.GET; diff --git a/examples/apps/next/app/api/stream-sessions/route.ts b/examples/apps/next/app/api/stream-sessions/route.ts new file mode 100644 index 0000000..296725f --- /dev/null +++ b/examples/apps/next/app/api/stream-sessions/route.ts @@ -0,0 +1,9 @@ +import { + dynamic, + maxDuration, + runtime, + streamingRoutes, +} from '../../../lib/routes.js'; + +export { runtime, dynamic, maxDuration }; +export const POST = streamingRoutes.sessions.POST; diff --git a/examples/apps/next/lib/routes.ts b/examples/apps/next/lib/routes.ts new file mode 100644 index 0000000..348f3ec --- /dev/null +++ b/examples/apps/next/lib/routes.ts @@ -0,0 +1,16 @@ +import { + createNextReviewRouteHandlers, + createNextStreamingRouteHandlers, + dynamic as nextDynamic, + maxDuration as nextMaxDuration, + runtime as nextRuntime, +} from '../../../next-app-router.js'; +import { createNextAiSdkUiRoute } from '../../../next-ai-sdk-ui.js'; + +export const runtime = nextRuntime; +export const dynamic = nextDynamic; +export const maxDuration = nextMaxDuration; + +export const reviewRoutes = createNextReviewRouteHandlers(); +export const streamingRoutes = createNextStreamingRouteHandlers(); +export const chatRoute = createNextAiSdkUiRoute(); diff --git a/examples/branching.ts b/examples/branching.ts new file mode 100644 index 0000000..3f89c88 --- /dev/null +++ b/examples/branching.ts @@ -0,0 +1,138 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + generateExampleText, + isMain, + prompt, +} from './_run.js'; + +const branchResultSchema = z.object({ + docs: z.string(), + issues: z.string(), + code: z.string(), +}); + +const summarySchema = z.object({ + summary: z.string(), +}); + +export function createBranchingExample( + options: { + analyzeDocs?: (topic: string) => Promise; + analyzeIssues?: (topic: string) => Promise; + analyzeCode?: (topic: string) => Promise; + summarize?: (parts: { + docs: string; + issues: string; + code: string; + }) => Promise>; + } = {} +) { + return createAgentMachine({ + id: 'branching-example', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + docs: z.string().nullable(), + issues: z.string().nullable(), + code: z.string().nullable(), + summary: z.string().nullable(), + }), + }, + context: (input) => ({ + topic: input.topic, + docs: null as string | null, + issues: null as string | null, + code: null as string | null, + summary: null as string | null, + }), + initial: 'analyzing', + states: { + analyzing: { + schemas: { output: branchResultSchema }, + invoke: async ({ context }) => { + const [docs, issues, code] = await Promise.all([ + (options.analyzeDocs + ?? ((topic) => + generateExampleText({ + system: 'You are a repository docs analyst. Be concise and concrete.', + prompt: `Summarize what the documentation angle should cover for this topic in 2 short sentences:\n\n${topic}`, + })))(context.topic), + (options.analyzeIssues + ?? ((topic) => + generateExampleText({ + system: 'You analyze likely issue patterns and risks. Be concise and concrete.', + prompt: `Summarize the likely issue and operational concerns for this topic in 2 short sentences:\n\n${topic}`, + })))(context.topic), + (options.analyzeCode + ?? ((topic) => + generateExampleText({ + system: 'You analyze code-level implementation concerns. Be concise and concrete.', + prompt: `Summarize the likely code architecture and implementation concerns for this topic in 2 short sentences:\n\n${topic}`, + })))(context.topic), + ]); + + return { docs, issues, code }; + }, + onDone: ({ output }) => ({ + target: 'summarizing', + context: output, + }), + }, + summarizing: { + schemas: { output: summarySchema }, + invoke: async ({ context }) => + (options.summarize + ?? (({ docs, issues, code }) => + generateExampleObject({ + schema: summarySchema, + system: 'You synthesize technical analysis into a concise summary.', + prompt: [ + 'Combine these three perspectives into a concise high-level summary.', + '', + `Docs:\n${docs}`, + '', + `Issues:\n${issues}`, + '', + `Code:\n${code}`, + ].join('\n'), + })))({ + docs: context.docs ?? '', + issues: context.issues ?? '', + code: context.code ?? '', + }), + onDone: ({ output }) => ({ + target: 'done', + context: { summary: output.summary }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + docs: context.docs, + issues: context.issues, + code: context.code, + summary: context.summary, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const machine = createBranchingExample(); + const result = await machine.execute(machine.getInitialState({ topic })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/chatbot-messages.ts b/examples/chatbot-messages.ts new file mode 100644 index 0000000..fa6e2ca --- /dev/null +++ b/examples/chatbot-messages.ts @@ -0,0 +1,148 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, + type AgentMessage, +} from '../src/index.js'; +import { + closePrompt, + generateExampleObject, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; + +const messageSchema = z.object({ + role: z.string(), + content: z.string(), +}); + +const replySchema = z.object({ + message: messageSchema, +}); + +export function createChatbotMessagesExample( + reply: (messages: AgentMessage[]) => Promise> = (messages) => + generateExampleObject({ + schema: replySchema, + system: 'You are a concise assistant in a terminal chat.', + prompt: [ + 'Write the next assistant message for this conversation.', + '', + ...messages.map((message) => `${message.role}: ${message.content}`), + ].join('\n'), + }) +) { + return createAgentMachine({ + id: 'chatbot-messages-example', + schemas: { + output: z.object({ + messages: z.array(messageSchema), + finalMessage: messageSchema.nullable(), + }), + events: { + 'messages.user': z.object({ + message: messageSchema.extend({ + role: z.literal('user'), + }), + }), + 'messages.end': z.object({}), + }, + }, + context: () => ({ + finalMessage: null as z.infer | null, + ended: false, + }), + messages: [], + initial: 'waitingForUser', + states: { + waitingForUser: { + on: { + 'messages.user': ({ event, messages }) => ({ + target: 'replying', + messages: messages.concat(event.message), + }), + 'messages.end': { + target: 'done', + context: { ended: true }, + }, + }, + }, + replying: { + schemas: { output: replySchema }, + invoke: async ({ messages }) => reply(messages), + onDone: ({ output, messages }) => ({ + target: 'waitingForUser', + messages: messages.concat(output.message), + context: { + finalMessage: output.message, + }, + }), + }, + done: { + type: 'final', + output: ({ context, messages }) => ({ + messages, + finalMessage: context.finalMessage, + }), + }, + }, + }); +} + +async function main() { + try { + const machine = createChatbotMessagesExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + let lastPrintedAssistantMessage: string | null = null; + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + messages: snapshot.messages, + output: snapshot.output, + }); + break; + } + + const finalMessage = snapshot.context.finalMessage as + | z.infer + | null; + + if ( + finalMessage?.role === 'assistant' + && finalMessage.content !== lastPrintedAssistantMessage + ) { + console.log(`Assistant: ${finalMessage.content}`); + lastPrintedAssistantMessage = finalMessage.content; + } + + const content = await prompt('User (blank to exit)'); + await run.send( + content + ? { + type: 'messages.user', + message: { role: 'user', content }, + } + : { type: 'messages.end' } + ); + } + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/chatbot.ts b/examples/chatbot.ts index 122f664..b062664 100644 --- a/examples/chatbot.ts +++ b/examples/chatbot.ts @@ -1,71 +1,184 @@ import { z } from 'zod'; -import { createAgent, fromDecision } from '../src'; -import { openai } from '@ai-sdk/openai'; -import { assign, createActor, log, setup } from 'xstate'; -import { fromTerminal } from './helpers/helpers'; +import { + createAgentMachine, + createMemoryRunStore, + decide, + decideResultSchema, + startSession, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + generateExampleObject, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; -const agent = createAgent({ - name: 'chatbot', - model: openai('gpt-4-turbo'), - events: { - 'agent.respond': z.object({ - response: z.string().describe('The response from the agent'), - }), - 'agent.endConversation': z.object({}).describe('Stop the conversation'), - }, - context: { - userMessage: z.string(), - }, +const replySchema = z.object({ + response: z.string(), }); -const machine = setup({ - types: agent.types, - actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, -}).createMachine({ - initial: 'listening', - context: { - userMessage: '', - }, - states: { - listening: { - invoke: { - src: 'getFromTerminal', - input: 'User:', - onDone: { - actions: assign({ - userMessage: ({ event }) => event.output, +export function createChatbotExample( + options: { + adapter?: DecideAdapter; + reply?: (transcript: string[]) => Promise>; + } = {} +) { + const decisionOptions = { + respond: { description: 'Reply to the user and continue chatting.' }, + end: { description: 'End the conversation now.' }, + } as const; + + const adapter = + options.adapter ?? + (process.env.OPENAI_API_KEY ? createOpenAiDecisionAdapter() : undefined); + const reply = + options.reply ?? + ((transcript: string[]) => + generateExampleObject({ + schema: replySchema, + system: 'You are a concise, helpful assistant in a terminal chat.', + prompt: [ + 'Write the assistant reply for the conversation below.', + 'Keep it short and directly responsive.', + '', + transcript.join('\n'), + ].join('\n'), + })); + + return createAgentMachine({ + id: 'chatbot-example', + schemas: { + output: z.object({ + transcript: z.array(z.string()), + ended: z.boolean(), + lastAssistantMessage: z.string().nullable(), + }), + events: { + 'user.message': z.object({ message: z.string() }), + 'user.exit': z.object({}), + }, + }, + context: () => ({ + transcript: [] as string[], + lastUserMessage: null as string | null, + lastAssistantMessage: null as string | null, + ended: false, + }), + initial: 'listening', + states: { + listening: { + on: { + 'user.message': ({ event, context }) => ({ + target: 'deciding', + context: { + lastUserMessage: event.message, + transcript: [...context.transcript, `User: ${event.message}`], + }, }), - target: 'responding', + 'user.exit': { + target: 'done', + context: { ended: true }, + }, }, }, - }, - responding: { - invoke: { - src: 'agent', - input: ({ context }) => ({ + deciding: { + schemas: { output: decideResultSchema(decisionOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'Decide whether the assistant should answer or end the conversation.', + 'End only when the user is clearly saying goodbye or asking to stop.', + '', + context.transcript.join('\n'), + ].join('\n'), + options: decisionOptions, + }), + onDone: ({ output }) => ({ + target: output.choice === 'end' ? 'done' : 'replying', + context: output.choice === 'end' ? { ended: true } : {}, + }), + }, + replying: { + schemas: { output: replySchema }, + invoke: async ({ context }) => reply(context.transcript), + onDone: ({ output, context }) => ({ + target: 'listening', context: { - userMessage: 'User says: ' + context.userMessage, + lastAssistantMessage: output.response, + transcript: [...context.transcript, `Assistant: ${output.response}`], }, - messages: agent.getMessages(), - goal: 'Respond to the user, unless they want to end the conversation.', }), }, - on: { - 'agent.respond': { - actions: log(({ event }) => `Agent: ${event.response}`), - target: 'listening', - }, - 'agent.endConversation': 'finished', + done: { + type: 'final', + output: ({ context }) => ({ + transcript: context.transcript, + ended: context.ended, + lastAssistantMessage: context.lastAssistantMessage, + }), }, }, - finished: { - type: 'final', - }, - }, - exit: () => { - console.log('End of conversation.'); - process.exit(); - }, -}); + }); +} + +async function main() { + try { + const machine = createChatbotExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + let lastPrintedAssistantMessage: string | null = null; + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + if ( + snapshot.output && + typeof snapshot.output === 'object' && + 'lastAssistantMessage' in snapshot.output && + snapshot.output.lastAssistantMessage && + snapshot.output.lastAssistantMessage !== lastPrintedAssistantMessage + ) { + console.log(`Assistant: ${snapshot.output.lastAssistantMessage}`); + } + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + output: snapshot.output, + }); + break; + } + + if ( + snapshot.context.lastAssistantMessage && + snapshot.context.lastAssistantMessage !== lastPrintedAssistantMessage + ) { + console.log(`Assistant: ${snapshot.context.lastAssistantMessage}`); + lastPrintedAssistantMessage = snapshot.context.lastAssistantMessage; + } + + const message = await prompt('User (blank to exit)'); + await run.send( + message + ? { type: 'user.message', message } + : { type: 'user.exit' } + ); + } + } finally { + closePrompt(); + } +} -createActor(machine).start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/classify.ts b/examples/classify.ts new file mode 100644 index 0000000..dcbc96a --- /dev/null +++ b/examples/classify.ts @@ -0,0 +1,73 @@ +import { z } from 'zod'; +import { + createAgentMachine, + classify, + classifyResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + formatResult, + isMain, + prompt, +} from './_run.js'; + +export function createClassifyExample( + adapter: DecideAdapter = createOpenAiDecisionAdapter() +) { + const categories = { + billing: { description: 'Payments, invoices, refunds, and charges.' }, + technical: { description: 'Bugs, outages, and product issues.' }, + general: { description: 'Everything else.' }, + } as const; + + return createAgentMachine({ + id: 'classify-example', + schemas: { + input: z.object({ request: z.string() }), + output: z.object({ category: z.string().nullable() }), + }, + context: (input) => ({ + request: input.request, + category: null as string | null, + }), + initial: 'routing', + states: { + routing: { + schemas: { output: classifyResultSchema(categories) }, + invoke: async ({ context }) => + classify({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: `Classify this support request:\n\n${context.request}`, + into: categories, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { category: output.category }, + }), + }, + done: { + // use input; category should always be defined when entering + type: 'final', + output: ({ context }) => ({ category: context.category }), + }, + }, + }); +} + +async function main() { + try { + const request = await prompt('Support request'); + const machine = createClassifyExample(); + + console.log(formatResult(await machine.execute(machine.getInitialState({ request })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/cloudflare-agents.ts b/examples/cloudflare-agents.ts new file mode 100644 index 0000000..2b6cbdc --- /dev/null +++ b/examples/cloudflare-agents.ts @@ -0,0 +1,126 @@ +import { + restoreSession, + startSession, + type RunStore, +} from '../src/index.js'; +import { + createCloudflareAgentRunStore, + type CloudflareAgentRunStoreState, +} from '../src/cloudflare/index.js'; +import { createPersistenceExample } from './persistence.js'; + +export { + createCloudflareAgentRunStore, + type CloudflareAgentRunStoreState, +}; + +export interface CloudflareAgentsExampleArtifacts { + ReviewWorkflowAgent: new (...args: any[]) => { + onRequest(request: Request): Promise; + }; + worker: { + fetch(request: Request, env: Record): Promise; + }; +} + +/** + * Cloudflare's `agents` package imports `cloudflare:` modules, so this example + * keeps that import lazy to stay loadable in plain Node. In a real Worker, + * move the `agents` imports to top-level imports. + */ +export async function createCloudflareAgentsExample(): Promise { + const { Agent, routeAgentRequest } = await import('agents'); + const machine = createPersistenceExample(); + + class ReviewWorkflowAgent extends Agent< + Record, + CloudflareAgentRunStoreState + > { + initialState: CloudflareAgentRunStoreState = { + sessions: {}, + }; + + private getStore(): RunStore { + return createCloudflareAgentRunStore({ + getState: () => this.state ?? this.initialState, + setState: (nextState) => this.setState(nextState), + }); + } + + async onRequest(request: Request): Promise { + const url = new URL(request.url); + + if (request.method === 'POST' && url.pathname.endsWith('/start')) { + const body = await request.json() as { request: string }; + const run = await startSession(machine, { + store: this.getStore(), + input: { + request: body.request, + }, + }); + + return Response.json({ + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'POST' && url.pathname.endsWith('/events')) { + const body = await request.json() as { + sessionId: string; + event: { type: 'approve' }; + }; + const run = await restoreSession(machine, { + sessionId: body.sessionId, + store: this.getStore(), + }); + + await run.send(body.event); + + return Response.json({ + sessionId: body.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'GET' && url.pathname.endsWith('/snapshot')) { + const sessionId = requiredSessionId(url); + const run = await restoreSession(machine, { + sessionId, + store: this.getStore(), + }); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + return new Response('Not found', { status: 404 }); + } + } + + const worker = { + async fetch(request: Request, env: Record) { + return ( + await routeAgentRequest(request, env, { + prefix: '/agents', + }) + ) ?? new Response('Not found', { status: 404 }); + }, + }; + + return { + ReviewWorkflowAgent, + worker, + } satisfies CloudflareAgentsExampleArtifacts; +} + +function requiredSessionId(url: URL): string { + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId) { + throw new Error('Missing sessionId'); + } + + return sessionId; +} diff --git a/examples/cloudflare-durable-network.ts b/examples/cloudflare-durable-network.ts new file mode 100644 index 0000000..bca3d33 --- /dev/null +++ b/examples/cloudflare-durable-network.ts @@ -0,0 +1,105 @@ +import { + restoreSession, + startSession, + type AgentSnapshot, +} from '../src/index.js'; +import { + createDurableObjectRunStore, + type DurableObjectStateLike, +} from './cloudflare-durable-object.js'; +import { createMultiAgentNetworkExample } from './multi-agent-network.js'; + +export class AgentNetworkDurableObject { + private readonly store; + + constructor(private readonly state: DurableObjectStateLike) { + this.store = createDurableObjectRunStore(state.storage); + } + + async fetch(request: Request): Promise { + const url = new URL(request.url); + const machine = createMultiAgentNetworkExample({ + adapter: { + decide: async ({ prompt }) => { + if (!prompt.includes('Notes: none yet')) { + if (!prompt.includes('Current draft: none yet')) { + return { choice: 'finalize', data: {} }; + } + + return { + choice: 'write', + data: { angle: 'turn the current notes into a concise summary' }, + }; + } + + return { + choice: 'research', + data: { focus: 'collect the strongest supporting facts' }, + }; + }, + }, + research: async ({ topic, focus }) => ({ + notes: [`${topic}:${focus}:1`, `${topic}:${focus}:2`], + }), + write: async ({ topic, notes, angle }) => ({ + draft: `${topic} | ${angle} | ${notes.join(' / ')}`, + }), + }); + + if (request.method === 'POST' && url.pathname === '/start') { + const body = await request.json() as { topic: string }; + const run = await startSession(machine, { + store: this.store, + input: { topic: body.topic }, + }); + + return Response.json({ + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'POST' && url.pathname === '/resume') { + const sessionId = requiredSessionId(url); + const run = await restoreSession(machine, { + sessionId, + store: this.store, + }); + const snapshot = await waitForTerminalSnapshot(run.getSnapshot, 1000); + + return Response.json({ + sessionId, + snapshot, + }); + } + + return new Response('Not found', { status: 404 }); + } +} + +async function waitForTerminalSnapshot( + getSnapshot: () => AgentSnapshot, + timeoutMs: number +) { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + const snapshot = getSnapshot(); + if (snapshot.status === 'done' || snapshot.status === 'error') { + return snapshot; + } + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + return getSnapshot(); +} + +function requiredSessionId(url: URL): string { + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId) { + throw new Error('Missing sessionId'); + } + + return sessionId; +} diff --git a/examples/cloudflare-durable-object.ts b/examples/cloudflare-durable-object.ts new file mode 100644 index 0000000..23a040b --- /dev/null +++ b/examples/cloudflare-durable-object.ts @@ -0,0 +1,84 @@ +import { + restoreSession, + startSession, + type RunStore, +} from '../src/index.js'; +import { + createDurableObjectRunStore, + type DurableObjectStateLike, + type DurableObjectStorageLike, +} from '../src/cloudflare/index.js'; +import { createPersistenceExample } from './persistence.js'; + +export { + createDurableObjectRunStore, + type DurableObjectStateLike, + type DurableObjectStorageLike, +}; + +export class AgentSessionDurableObject { + private readonly store: RunStore; + + constructor(private readonly state: DurableObjectStateLike) { + this.store = createDurableObjectRunStore(state.storage); + } + + async fetch(request: Request): Promise { + const url = new URL(request.url); + const machine = createPersistenceExample(async ({ request, approved }) => ({ + summary: `${request} :: approved=${String(approved)}`, + })); + + if (request.method === 'POST' && url.pathname === '/start') { + const body = await request.json() as { request: string }; + const run = await startSession(machine, { + store: this.store, + input: { request: body.request }, + }); + + return Response.json({ + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'POST' && url.pathname === '/approve') { + const sessionId = requiredSessionId(url); + const run = await restoreSession(machine, { + store: this.store, + sessionId, + }); + + await run.send({ type: 'approve' }); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'GET' && url.pathname === '/status') { + const sessionId = requiredSessionId(url); + const run = await restoreSession(machine, { + store: this.store, + sessionId, + }); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + return new Response('Not found', { status: 404 }); + } +} + +function requiredSessionId(url: URL): string { + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId) { + throw new Error('Missing sessionId'); + } + + return sessionId; +} diff --git a/examples/conditional-subflow.ts b/examples/conditional-subflow.ts new file mode 100644 index 0000000..391adbc --- /dev/null +++ b/examples/conditional-subflow.ts @@ -0,0 +1,205 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const modeSchema = z.enum(['research', 'draft']); + +const researchSchema = z.object({ + bullets: z.array(z.string()), +}); + +const draftSchema = z.object({ + draft: z.string(), +}); + +export function createConditionalSubflowExample( + options: { + research?: (topic: string) => Promise>; + draft?: (args: { + topic: string; + bullets: string[]; + }) => Promise>; + } = {} +) { + const researchMachine = createAgentMachine({ + id: 'conditional-subflow-research', + schemas: { + input: z.object({ topic: z.string() }), + output: researchSchema, + }, + context: (input) => ({ + topic: input.topic, + bullets: [] as string[], + }), + initial: 'researching', + states: { + researching: { + schemas: { output: researchSchema }, + invoke: async ({ context }) => + (options.research + ?? ((topic) => + generateExampleObject({ + schema: researchSchema, + system: 'Return concise research bullets.', + prompt: `Return 2 to 4 bullets about ${topic}.`, + })))(context.topic), + onDone: ({ output }) => ({ + target: 'done', + context: { bullets: output.bullets }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ bullets: context.bullets }), + }, + }, + }); + + const draftMachine = createAgentMachine({ + id: 'conditional-subflow-draft', + schemas: { + input: z.object({ + topic: z.string(), + bullets: z.array(z.string()), + }), + output: draftSchema, + }, + context: (input) => ({ + topic: input.topic, + bullets: input.bullets, + draft: null as string | null, + }), + initial: 'drafting', + states: { + drafting: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => + (options.draft + ?? (({ topic, bullets }) => + generateExampleObject({ + schema: draftSchema, + system: 'Turn bullets into a short draft.', + prompt: [ + `Topic: ${topic}`, + 'Bullets:', + ...bullets.map((bullet) => `- ${bullet}`), + ].join('\n'), + })))({ + topic: context.topic, + bullets: context.bullets, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ draft: context.draft ?? '' }), + }, + }, + }); + + return createAgentMachine({ + id: 'conditional-subflow-example', + schemas: { + input: z.object({ + topic: z.string(), + mode: modeSchema, + bullets: z.array(z.string()).optional(), + }), + output: z.object({ + mode: modeSchema, + bullets: z.array(z.string()), + draft: z.string().nullable(), + }), + }, + context: (input) => ({ + topic: input.topic, + mode: input.mode, + bullets: input.bullets ?? [], + draft: null as string | null, + }), + initial: ({ context }) => + context.mode === 'research' + ? { target: 'researching' } + : { target: 'drafting', input: { bullets: context.bullets } }, + states: { + researching: { + schemas: { output: researchSchema }, + invoke: async ({ context }) => { + const result = await researchMachine.execute( + researchMachine.getInitialState({ topic: context.topic }) + ); + + if (result.status !== 'done') { + throw new Error('Research subflow did not finish'); + } + + return result.output; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { bullets: output.bullets }, + }), + }, + drafting: { + schemas: { input: z.object({ + bullets: z.array(z.string()), + }), output: draftSchema }, + invoke: async ({ context, input }) => { + const result = await draftMachine.execute( + draftMachine.getInitialState({ + topic: context.topic, + bullets: input.bullets, + }) + ); + + if (result.status !== 'done') { + throw new Error('Draft subflow did not finish'); + } + + return result.output; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + mode: context.mode, + bullets: context.bullets, + draft: context.draft, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const modeInput = await prompt('Mode (research/draft)'); + const mode = modeInput === 'draft' ? 'draft' : 'research'; + const machine = createConditionalSubflowExample(); + const result = await machine.execute( + machine.getInitialState({ topic, mode }) + ); + + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/content-creator-flow.ts b/examples/content-creator-flow.ts new file mode 100644 index 0000000..b6d579d --- /dev/null +++ b/examples/content-creator-flow.ts @@ -0,0 +1,141 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const routeSchema = z.object({ + route: z.enum(['blog', 'linkedin', 'research']), +}); + +const contentSchema = z.object({ + title: z.string(), + body: z.string(), +}); + +type ContentRoute = z.infer['route']; + +export function createContentCreatorFlowExample(options: { + routeRequest?: (request: string) => Promise>; + createBlog?: (request: string) => Promise>; + createLinkedInPost?: (request: string) => Promise>; + createResearchReport?: (request: string) => Promise>; +} = {}) { + const routeRequest = + options.routeRequest ?? + ((request: string) => + generateExampleObject({ + schema: routeSchema, + system: + 'Route content requests to blog, linkedin, or research. Choose research for analysis-heavy requests, linkedin for short professional posts, and blog for longer educational pieces.', + prompt: request, + })); + + const createBlog = + options.createBlog ?? + ((request: string) => + generateExampleObject({ + schema: contentSchema, + system: 'Write a concise professional blog post.', + prompt: request, + })); + + const createLinkedInPost = + options.createLinkedInPost ?? + ((request: string) => + generateExampleObject({ + schema: contentSchema, + system: 'Write a concise professional LinkedIn post.', + prompt: request, + })); + + const createResearchReport = + options.createResearchReport ?? + ((request: string) => + generateExampleObject({ + schema: contentSchema, + system: 'Write a concise research-style briefing with findings and implications.', + prompt: request, + })); + + return createAgentMachine({ + id: 'content-creator-flow-example', + schemas: { + input: z.object({ + request: z.string(), + }), + output: z.object({ + route: z.enum(['blog', 'linkedin', 'research']).nullable(), + title: z.string().nullable(), + body: z.string().nullable(), + }), + }, + context: (input) => ({ + request: input.request, + route: null as ContentRoute | null, + title: null as string | null, + body: null as string | null, + }), + initial: 'routing', + states: { + routing: { + schemas: { output: routeSchema }, + invoke: async ({ context }) => routeRequest(context.request), + onDone: ({ output }) => ({ + target: 'creating', + context: { + route: output.route, + }, + }), + }, + creating: { + schemas: { output: contentSchema }, + invoke: async ({ context }) => { + switch (context.route) { + case 'linkedin': + return createLinkedInPost(context.request); + case 'research': + return createResearchReport(context.request); + case 'blog': + default: + return createBlog(context.request); + } + }, + onDone: ({ output }) => ({ + target: 'done', + context: { + title: output.title, + body: output.body, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + route: context.route, + title: context.title, + body: context.body, + }), + }, + }, + }); +} + +async function main() { + try { + const request = await prompt('Content request'); + const machine = createContentCreatorFlowExample(); + const result = await machine.execute(machine.getInitialState({ request })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/customer-service-sim.ts b/examples/customer-service-sim.ts new file mode 100644 index 0000000..a7f1e2a --- /dev/null +++ b/examples/customer-service-sim.ts @@ -0,0 +1,139 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const serviceReplySchema = z.object({ + response: z.string(), +}); + +const customerReplySchema = z.object({ + response: z.string(), + done: z.boolean(), + outcome: z.string().nullable(), +}); + +type TranscriptContext = { + issue: string; + transcript: string[]; + turnCount: number; + maxTurns: number; + outcome: string | null; +}; + +export function createCustomerServiceSimExample( + options: { + serviceReply?: (context: TranscriptContext) => Promise>; + customerReply?: (context: TranscriptContext) => Promise>; + maxTurns?: number; + } = {} +) { + const serviceReply = + options.serviceReply ?? + ((context: TranscriptContext) => + generateExampleObject({ + schema: serviceReplySchema, + system: 'You are a customer support agent negotiating calmly and pragmatically.', + prompt: [ + `Issue: ${context.issue}`, + `Turn count: ${context.turnCount}`, + `Current outcome: ${context.outcome ?? 'none'}`, + '', + 'Transcript so far:', + context.transcript.join('\n'), + '', + 'Write the next support agent response in one short paragraph.', + ].join('\n'), + })); + const customerReply = + options.customerReply ?? + ((context: TranscriptContext) => + generateExampleObject({ + schema: customerReplySchema, + system: 'You are the customer in the support exchange. Stay realistic and concise.', + prompt: [ + `Original issue: ${context.issue}`, + `Turn count: ${context.turnCount}`, + '', + 'Transcript so far:', + context.transcript.join('\n'), + '', + 'Write the next customer reply. Set done=true only if the issue is resolved or the customer accepts the proposed outcome. Use outcome to summarize the result when done.', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'customer-service-sim-example', + schemas: { + input: z.object({ issue: z.string() }), + output: z.object({ + transcript: z.array(z.string()), + turnCount: z.number(), + outcome: z.string().nullable(), + }), + }, + context: (input) => ({ + issue: input.issue, + transcript: [`Customer: ${input.issue}`], + turnCount: 0, + maxTurns: options.maxTurns ?? 4, + outcome: null as string | null, + }), + initial: 'service', + states: { + service: { + schemas: { output: serviceReplySchema }, + invoke: async ({ context }) => serviceReply(context), + onDone: ({ output, context }) => ({ + target: context.turnCount + 1 >= context.maxTurns ? 'done' : 'customer', + context: { + transcript: [...context.transcript, `Agent: ${output.response}`], + outcome: + context.turnCount + 1 >= context.maxTurns + ? 'max-turns-reached' + : context.outcome, + }, + }), + }, + customer: { + schemas: { output: customerReplySchema }, + invoke: async ({ context }) => customerReply(context), + onDone: ({ output, context }) => ({ + target: output.done ? 'done' : 'service', + context: { + transcript: [...context.transcript, `Customer: ${output.response}`], + turnCount: context.turnCount + 1, + outcome: output.outcome, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + transcript: context.transcript, + turnCount: context.turnCount, + outcome: context.outcome, + }), + }, + }, + }); +} + +async function main() { + try { + const issue = await prompt('Customer issue'); + const machine = createCustomerServiceSimExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ issue })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/decide.ts b/examples/decide.ts new file mode 100644 index 0000000..a484150 --- /dev/null +++ b/examples/decide.ts @@ -0,0 +1,94 @@ +import { z } from 'zod'; +import { + createAgentMachine, + decide, + decideResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + formatResult, + isMain, + prompt, +} from './_run.js'; + +export function createDecideExample(adapter: DecideAdapter = createOpenAiDecisionAdapter()) { + const triageOptions = { + reply: { + description: 'Reply directly to the customer.', + schema: z.object({ message: z.string() }), + }, + askForClarification: { + description: 'Ask one follow-up question before proceeding.', + schema: z.object({ question: z.string() }), + }, + escalate: { + description: 'Escalate to a human specialist.', + schema: z.object({ team: z.string() }), + }, + } as const; + + return createAgentMachine({ + id: 'decide-example', + schemas: { + input: z.object({ request: z.string() }), + output: z.object({ + action: z.string().nullable(), + payload: z.record(z.string(), z.unknown()).nullable(), + }), + }, + context: (input) => ({ + request: input.request, + action: null as string | null, + payload: null as Record | null, + }), + initial: 'triage', + states: { + triage: { + schemas: { output: decideResultSchema(triageOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'Choose the best next step for this support request.', + 'Prefer asking a single clarification question when key facts are missing.', + '', + `Request: ${context.request}`, + ].join('\n'), + options: triageOptions, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { + action: output.choice, + payload: output.data, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + action: context.action, + payload: context.payload, + }), + }, + }, + }); +} + +async function main() { + try { + const request = await prompt('Support request'); + const machine = createDecideExample(); + + console.log(formatResult(await machine.execute(machine.getInitialState({ request })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/email-auto-responder-flow.ts b/examples/email-auto-responder-flow.ts new file mode 100644 index 0000000..2d1bf85 --- /dev/null +++ b/examples/email-auto-responder-flow.ts @@ -0,0 +1,228 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + restoreSession, + startSession, + type RunStore, +} from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const incomingEmailSchema = z.object({ + id: z.string(), + subject: z.string(), + body: z.string(), + sender: z.string(), +}); + +const draftResponseSchema = z.object({ + draft: z.string(), +}); + +type IncomingEmail = z.infer; + +export function createEmailAutoResponderFlowExample( + createDraft: (email: IncomingEmail) => Promise> = ( + email + ) => + generateExampleObject({ + schema: draftResponseSchema, + system: 'Write a concise professional email reply draft.', + prompt: [ + `Sender: ${email.sender}`, + `Subject: ${email.subject}`, + '', + email.body, + ].join('\n'), + }) +) { + return createAgentMachine({ + id: 'email-auto-responder-flow-example', + schemas: { + input: z.object({}), + output: z.object({ + processedIds: z.array(z.string()), + drafts: z.record(z.string(), z.string()), + }), + events: { + 'emails.received': z.object({ + emails: z.array(incomingEmailSchema), + }), + stop: z.object({}), + }, + }, + context: () => ({ + queue: [] as IncomingEmail[], + currentEmail: null as IncomingEmail | null, + processedIds: [] as string[], + drafts: {} as Record, + }), + initial: 'waiting', + states: { + waiting: { + on: { + 'emails.received': ({ context, event }) => { + const nextQueue = [...context.queue, ...event.emails].filter( + (email) => + !context.processedIds.includes(email.id) + && email.id !== context.currentEmail?.id + ); + const [currentEmail, ...queue] = nextQueue; + + if (!currentEmail) { + return { + context: { + queue, + }, + }; + } + + return { + target: 'drafting', + context: { + currentEmail, + queue, + }, + }; + }, + stop: { + target: 'done', + }, + }, + }, + drafting: { + on: { + 'emails.received': ({ context, event }) => ({ + context: { + queue: [...context.queue, ...event.emails].filter( + (email) => + !context.processedIds.includes(email.id) + && email.id !== context.currentEmail?.id + ), + }, + }), + stop: { + target: 'done', + }, + }, + schemas: { output: draftResponseSchema }, + invoke: async ({ context }) => createDraft(context.currentEmail!), + onDone: ({ output, context }) => { + const currentEmail = context.currentEmail!; + const processedIds = [...context.processedIds, currentEmail.id]; + const drafts = { + ...context.drafts, + [currentEmail.id]: output.draft, + }; + const [nextEmail, ...queue] = context.queue; + + if (nextEmail) { + return { + target: 'drafting', + context: { + currentEmail: nextEmail, + queue, + processedIds, + drafts, + }, + }; + } + + return { + target: 'waiting', + context: { + currentEmail: null, + queue: [], + processedIds, + drafts, + }, + }; + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + processedIds: context.processedIds, + drafts: context.drafts, + }), + }, + }, + }); +} + +export async function runEmailAutoResponderFlowExample( + emails: IncomingEmail[], + options: { + createDraft?: (email: IncomingEmail) => Promise>; + store?: RunStore; + } = {} +) { + const machine = createEmailAutoResponderFlowExample(options.createDraft); + const store = options.store ?? createMemoryRunStore(); + const run = await startSession(machine, { + store, + input: {}, + }); + + await run.send({ + type: 'emails.received', + emails, + }); + + return { + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + restoredSnapshot: ( + await restoreSession(machine, { + sessionId: run.sessionId, + store, + }) + ).getSnapshot(), + }; +} + +async function main() { + try { + const sender = await prompt('Sender'); + const subject = await prompt('Subject'); + const body = await prompt('Body'); + const result = await runEmailAutoResponderFlowExample([ + { + id: 'email-1', + sender, + subject, + body, + }, + ]); + + console.log(formatResult({ + status: + result.snapshot.status === 'done' + ? 'done' + : result.snapshot.status === 'error' + ? 'error' + : 'pending', + state: { + value: result.snapshot.value, + context: result.snapshot.context, + status: result.snapshot.status, + input: result.snapshot.input, + }, + output: result.snapshot.output, + context: result.snapshot.context, + error: result.snapshot.error, + } as never)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/email.ts b/examples/email.ts index e65a88c..391b753 100644 --- a/examples/email.ts +++ b/examples/email.ts @@ -1,118 +1,266 @@ import { z } from 'zod'; -import { createAgent, fromDecision } from '../src'; -import { openai } from '@ai-sdk/openai'; -import { assign, createActor, setup } from 'xstate'; -import { fromTerminal } from './helpers/helpers'; +import { + createAgentMachine, + createMemoryRunStore, + decide, + decideResultSchema, + startSession, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + generateExampleObject, + generateExampleText, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; -const agent = createAgent({ - name: 'email', - model: openai('gpt-4'), - events: { - askForClarification: z.object({ - questions: z.array(z.string()).describe('The questions to ask the agent'), - }), - submitEmail: z.object({ - email: z.string().describe('The email to submit'), - }), - }, +const draftSchema = z.object({ + replyEmail: z.string(), }); -const machine = setup({ - types: { - events: agent.types.events, - input: {} as { - email: string; - instructions: string; +type EmailTools = { + lookupContactName: (email: string) => Promise; + lookupAvailability: () => Promise; + createSignature: (name: string) => Promise; +}; + +export function createEmailExample( + options: { + adapter?: DecideAdapter; + tools?: Partial; + compose?: ( + input: { + email: string; + instructions: string; + clarifications: string[]; + contactName: string; + availability: string[]; + signature: string; + } + ) => Promise>; + } = {} +) { + const checkingOptions = { + askForClarification: { + description: 'Ask one or more clarifying questions before drafting.', + schema: z.object({ + questions: z.array(z.string()).min(1), + }), }, - context: {} as { - email: string; - instructions: string; - clarifications: string[]; - replyEmail: string | null; + draft: { + description: 'Draft the email reply now.', }, - }, - actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, -}).createMachine({ - initial: 'checking', - context: ({ input }) => ({ - email: input.email, - instructions: input.instructions, - clarifications: [], - replyEmail: null, - }), - states: { - checking: { - invoke: { - src: 'agent', - input: ({ context }) => ({ - context: { - email: context.email, - instructions: context.instructions, - clarifications: context.clarifications, - }, - messages: agent.getMessages(), - goal: 'Respond to the email given the instructions and the provided clarifications. If not enough information is provided, ask for clarification. Otherwise, if you are absolutely sure that there is no ambiguous or missing information, create and submit a response email.', - }), + } as const; + + const adapter = + options.adapter ?? + (process.env.OPENAI_API_KEY ? createOpenAiDecisionAdapter() : undefined); + const tools: EmailTools = { + lookupContactName: + options.tools?.lookupContactName ?? + (async (email) => { + const result = await generateExampleObject({ + schema: z.object({ name: z.string() }), + system: 'Infer a plausible recipient/contact name from an email thread when possible.', + prompt: `Infer the recipient or contact name from this email. If unclear, return a reasonable professional placeholder.\n\n${email}`, + }); + + return result.name; + }), + lookupAvailability: + options.tools?.lookupAvailability ?? + (async () => { + const result = await generateExampleObject({ + schema: z.object({ + availability: z.array(z.string()).min(2).max(3), + }), + system: 'Produce plausible professional meeting slots.', + prompt: + 'Return 2 or 3 plausible meeting times for next week, written in a concise natural style.', + }); + + return result.availability; + }), + createSignature: + options.tools?.createSignature ?? + (async (name) => + generateExampleText({ + system: 'Write a concise professional email signature.', + prompt: `Write a short professional sign-off for the sender named ${name}.`, + })), + }; + const compose = + options.compose ?? + (({ + email, + instructions, + clarifications, + contactName, + availability, + signature, + }) => + generateExampleObject({ + schema: draftSchema, + system: 'You write concise professional email replies.', + prompt: [ + `Incoming email:\n${email}`, + '', + `Instructions:\n${instructions}`, + '', + `Contact name: ${contactName}`, + `Availability: ${availability.join(' | ')}`, + `Signature:\n${signature}`, + clarifications.length + ? `Clarifications:\n${clarifications.map((item) => `- ${item}`).join('\n')}` + : 'Clarifications: none', + '', + 'Draft the reply email.', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'email-example', + schemas: { + input: z.object({ + email: z.string(), + instructions: z.string(), + }), + output: z.object({ + replyEmail: z.string().nullable(), + clarifications: z.array(z.string()), + }), + events: { + 'user.answer': z.object({ answer: z.string() }), }, - on: { - askForClarification: { - actions: ({ event }) => console.log(event.questions.join('\n')), - target: 'clarifying', - }, - submitEmail: { - target: 'submitting', + }, + context: (input) => ({ + email: input.email, + instructions: input.instructions, + clarifications: [] as string[], + questions: [] as string[], + replyEmail: null as string | null, + }), + initial: 'checking', + states: { + checking: { + schemas: { output: decideResultSchema(checkingOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'Decide whether there is enough information to draft the reply email.', + 'Choose askForClarification only if key scheduling or identity details are missing.', + '', + `Email: ${context.email}`, + `Instructions: ${context.instructions}`, + `Clarifications: ${context.clarifications.join(' | ') || 'none'}`, + ].join('\n'), + options: checkingOptions, + }), + onDone: ({ output, context }) => { + if ( + output.choice === 'askForClarification' + && context.clarifications.length === 0 + ) { + return { + target: 'clarifying', + context: { questions: output.data.questions }, + }; + } + + return { + target: 'drafting', + context: { questions: [] }, + }; }, }, - }, - clarifying: { - invoke: { - src: 'getFromTerminal', - input: `Please provide answers to the questions above`, - onDone: { - actions: assign({ - clarifications: ({ context, event }) => - context.clarifications.concat(event.output), + clarifying: { + on: { + 'user.answer': ({ event, context }) => ({ + target: 'checking', + context: { + clarifications: [...context.clarifications, event.answer], + questions: [], + }, }), - target: 'checking', }, }, - }, - submitting: { - invoke: { - src: 'agent', - input: ({ context }) => ({ - context: { + drafting: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => { + const contactName = await tools.lookupContactName(context.email); + const availability = await tools.lookupAvailability(); + const signature = await tools.createSignature(contactName); + + return compose({ email: context.email, instructions: context.instructions, clarifications: context.clarifications, - }, - goal: `Create and submit an email based on the instructions.`, + contactName, + availability, + signature, + }); + }, + onDone: ({ output }) => ({ + target: 'done', + context: { replyEmail: output.replyEmail }, }), }, - on: { - submitEmail: { - actions: assign({ - replyEmail: ({ event }) => event.email, - }), - target: 'done', - }, + done: { + type: 'final', + output: ({ context }) => ({ + replyEmail: context.replyEmail, + clarifications: context.clarifications, + }), }, }, - done: { - type: 'final', - entry: ({ context }) => console.log(context.replyEmail), - }, - }, - exit: () => { - console.log('End of conversation.'); - process.exit(); - }, -}); + }); +} + +async function main() { + try { + const email = await prompt('Incoming email'); + const instructions = await prompt('Instructions'); + const machine = createEmailExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { email, instructions }, + }); + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + output: snapshot.output, + }); + break; + } + + if (snapshot.value === 'clarifying') { + console.log(snapshot.context.questions.join('\n')); + const answer = await prompt('Clarification'); + await run.send({ type: 'user.answer', answer }); + continue; + } + + throw new Error('Email example entered an unexpected pending state.'); + } + } finally { + closePrompt(); + } +} -createActor(machine, { - input: { - email: 'That sounds great! When are you available?', - instructions: - 'Tell them exactly when I am available. Address them by his full (first and last) name.', - }, -}).start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/error-retry.ts b/examples/error-retry.ts new file mode 100644 index 0000000..ce422f6 --- /dev/null +++ b/examples/error-retry.ts @@ -0,0 +1,134 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const answerSchema = z.object({ + answer: z.string(), +}); + +export function createErrorRetryExample( + answer: (args: { + question: string; + attempt: number; + }) => Promise> = async ({ question, attempt }) => + generateExampleObject({ + schema: answerSchema, + system: 'Answer the user question in one concise paragraph.', + prompt: [ + `Attempt: ${attempt}`, + '', + `Question: ${question}`, + ].join('\n'), + }), + maxAttempts = 3 +) { + return createAgentMachine({ + id: 'error-retry-example', + schemas: { + input: z.object({ + question: z.string(), + }), + events: { + 'xstate.error.invoke.answering': z.object({ + type: z.literal('xstate.error.invoke.answering'), + error: z.unknown().optional(), + at: z.number().optional(), + }), + }, + output: z.object({ + answer: z.string().nullable(), + attempts: z.number().int().min(1), + errors: z.array(z.string()), + }), + }, + context: (input) => ({ + question: input.question, + answer: null as string | null, + attempt: 1, + errors: [] as string[], + }), + initial: 'answering', + states: { + answering: { + schemas: { output: answerSchema }, + invoke: async ({ context }) => + answer({ + question: context.question, + attempt: context.attempt, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { + answer: output.answer, + }, + }), + on: { + 'xstate.error.invoke.answering': ({ event, context }) => { + const errors = [...context.errors, formatError(event.error)]; + + if (context.attempt >= maxAttempts) { + return { + target: 'failed', + context: { errors }, + }; + } + + return { + target: 'answering', + context: { + attempt: context.attempt + 1, + errors, + }, + }; + }, + }, + }, + failed: { + type: 'final', + output: ({ context }) => ({ + answer: context.answer, + attempts: context.attempt, + errors: context.errors, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + answer: context.answer, + attempts: context.attempt, + errors: context.errors, + }), + }, + }, + }); +} + +function formatError(error: unknown): string { + if (error && typeof error === 'object' && 'message' in error) { + return String((error as { message: unknown }).message); + } + + return String(error); +} + +async function main() { + try { + const question = await prompt('Question'); + const machine = createErrorRetryExample(); + const result = await machine.execute(machine.getInitialState({ question })); + + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/hitl.ts b/examples/hitl.ts new file mode 100644 index 0000000..9d80bf2 --- /dev/null +++ b/examples/hitl.ts @@ -0,0 +1,146 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, + type AgentMessage, +} from '../src/index.js'; +import { + closePrompt, + generateExampleObject, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; + +const draftSchema = z.object({ + draft: z.string(), +}); + +export function createHitlExample( + draftReply: (args: { + task: string; + messages: AgentMessage[]; + }) => Promise> = async ({ task, messages }) => { + return generateExampleObject({ + schema: draftSchema, + prompt: [ + `Task: ${task}`, + '', + 'Use the notes below to draft a concise response:', + ...messages.map((message, index) => `${index + 1}. ${message.content}`), + ].join('\n'), + }); + } +) { + return createAgentMachine({ + id: 'hitl-example', + schemas: { + input: z.object({ task: z.string() }), + output: z.object({ + draft: z.string().nullable().optional(), + cancelled: z.literal(true).optional(), + }), + events: { + 'user.message': z.object({ message: z.string() }), + 'user.approve': z.object({}), + 'user.cancel': z.object({}), + }, + }, + context: (input) => ({ + task: input.task, + draft: null as string | null, + }), + messages: [], + initial: 'gathering', + states: { + gathering: { + on: { + 'user.message': ({ messages, event }) => ({ + messages: messages.concat({ role: 'user', content: event.message }), + }), + 'user.approve': { target: 'drafting' }, + 'user.cancel': { target: 'cancelled' }, + }, + }, + drafting: { + schemas: { output: draftSchema }, + invoke: async ({ context, messages }) => + draftReply({ + task: context.task, + messages, + }), + onDone: ({ output, messages }) => ({ + target: 'done', + messages: messages.concat({ role: 'assistant', content: output.draft }), + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ draft: context.draft ?? null }), + }, + cancelled: { + type: 'final', + output: () => ({ cancelled: true as const }), + }, + }, + }); +} + +async function main() { + try { + const task = await prompt('Task'); + const machine = createHitlExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { task }, + }); + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + messages: snapshot.messages, + output: snapshot.output, + }); + break; + } + + const message = await prompt('Add note, or type /approve or /cancel'); + + if (message === '/approve') { + await run.send({ type: 'user.approve' }); + continue; + } + + if (message === '/cancel') { + await run.send({ type: 'user.cancel' }); + continue; + } + + await run.send({ + type: 'user.message', + message, + }); + console.log({ + status: run.getSnapshot().status, + value: run.getSnapshot().value, + context: run.getSnapshot().context, + }); + } + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/http-session.ts b/examples/http-session.ts new file mode 100644 index 0000000..f765193 --- /dev/null +++ b/examples/http-session.ts @@ -0,0 +1,17 @@ +import { createSessionHttpHandler } from '../src/http/index.js'; +import { type RunStore } from '../src/index.js'; +import { createPersistenceExample } from './persistence.js'; + +export interface SessionHttpHandlerOptions { + store?: RunStore; + summarize?: Parameters[0]; +} + +export function createPersistenceSessionHttpHandler( + options: SessionHttpHandlerOptions = {} +) { + const machine = createPersistenceExample(options.summarize); + return createSessionHttpHandler(machine, { + store: options.store, + }); +} diff --git a/examples/http-streaming-session.ts b/examples/http-streaming-session.ts new file mode 100644 index 0000000..2f2a394 --- /dev/null +++ b/examples/http-streaming-session.ts @@ -0,0 +1,138 @@ +import { z } from 'zod'; +import { createSessionHttpController } from '../src/http/index.js'; +import { + createAgentMachine, + createMemoryRunStore, + type RunStore, +} from '../src/index.js'; + +const streamingInputSchema = z.object({ + streamId: z.string(), + text: z.string(), +}); + +const streamingOutputSchema = z.object({ + text: z.string(), +}); + +const textPartSchema = z.object({ + delta: z.string(), +}); + +export interface StreamingSessionHttpController { + handle(request: Request): Promise; + advance(streamId: string): void; + dropActiveSession(sessionId: string): void; +} + +export function createStreamingSessionHttpController(options: { + store?: RunStore; +} = {}): StreamingSessionHttpController { + const store = options.store ?? createMemoryRunStore(); + const streamer = createDurableChunkStreamer(); + const machine = createAgentMachine({ + id: 'http-streaming-session-example', + schemas: { + input: streamingInputSchema, + output: streamingOutputSchema, + emitted: { + textPart: textPartSchema, + }, + }, + context: (input) => ({ + streamId: input.streamId, + text: input.text, + finalText: '', + }), + initial: 'writing', + states: { + writing: { + schemas: { output: streamingOutputSchema }, + invoke: async ({ context }, enq) => + streamer.streamText(context.streamId, context.text, (delta) => { + enq.emit({ type: 'textPart', delta }); + }), + onDone: ({ output }) => ({ + target: 'done', + context: { finalText: output.text }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + text: context.finalText, + }), + }, + }, + }); + const controller = createSessionHttpController(machine, { store }); + + return { + advance(streamId) { + streamer.advance(streamId); + }, + + dropActiveSession(sessionId) { + controller.dropActiveSession(sessionId); + }, + + async handle(request) { + return controller.handle(request); + }, + }; +} + +function createDurableChunkStreamer() { + const cursors = new Map(); + const invocations = new Map(); + const waiters = new Map void>>(); + + return { + advance(streamId: string) { + const current = waiters.get(streamId) ?? []; + waiters.set(streamId, []); + for (const resolve of current) { + resolve(); + } + }, + + async streamText( + streamId: string, + text: string, + emit: (delta: string) => void + ) { + const chunks = splitIntoChunks(text); + const invocation = (invocations.get(streamId) ?? 0) + 1; + invocations.set(streamId, invocation); + let cursor = cursors.get(streamId) ?? 0; + + while (cursor < chunks.length) { + if (invocation < (invocations.get(streamId) ?? 0)) { + await new Promise(() => {}); + } + + await new Promise((resolve) => { + waiters.set(streamId, [...(waiters.get(streamId) ?? []), resolve]); + }); + + if (invocation < (invocations.get(streamId) ?? 0)) { + await new Promise(() => {}); + } + + emit(chunks[cursor]!); + cursor += 1; + cursors.set(streamId, cursor); + } + + return { text }; + }, + }; +} + +function splitIntoChunks(text: string): string[] { + if (text.length <= 3) { + return [text]; + } + + return [text.slice(0, 3), text.slice(3)]; +} diff --git a/examples/index.ts b/examples/index.ts new file mode 100644 index 0000000..c73aafb --- /dev/null +++ b/examples/index.ts @@ -0,0 +1,95 @@ +// Runtime and deployment examples +export { createPersistenceSessionHttpHandler } from './http-session.js'; +export { createStreamingSessionHttpController } from './http-streaming-session.js'; +export { createAiSdkExample } from './ai-sdk.js'; +export { + createCloudflareAgentRunStore, + createCloudflareAgentsExample, + type CloudflareAgentRunStoreState, +} from './cloudflare-agents.js'; +export { + AgentSessionDurableObject, + createDurableObjectRunStore, + type DurableObjectStateLike, + type DurableObjectStorageLike, +} from './cloudflare-durable-object.js'; +export { AgentNetworkDurableObject } from './cloudflare-durable-network.js'; +export { + createNextAiSdkUiRoute, + type AgentUiMessage, +} from './next-ai-sdk-ui.js'; +export { + createNextReviewRouteHandlers, + createNextStreamingRouteHandlers, + dynamic as nextAppRouterDynamic, + maxDuration as nextAppRouterMaxDuration, + runtime as nextAppRouterRuntime, + type NextRouteContext, +} from './next-app-router.js'; +export { createPersistenceExample, runPersistenceExample } from './persistence.js'; +export { + createPersistentMultiAgentNetworkExample, + runPersistentMultiAgentNetworkExample, +} from './persistent-multi-agent-network.js'; +export { + createPersistentStreamingExample, + runPersistentStreamingExample, +} from './persistent-streaming.js'; +export { + createPersistentSupervisorExample, + runPersistentSupervisorExample, +} from './persistent-supervisor.js'; + +// Workflow examples +export { createContentCreatorFlowExample } from './content-creator-flow.js'; +export { + createEmailAutoResponderFlowExample, + runEmailAutoResponderFlowExample, +} from './email-auto-responder-flow.js'; +export { createErrorRetryExample } from './error-retry.js'; +export { createLeadScoreFlowExample } from './lead-score-flow.js'; +export { createMeetingAssistantFlowExample } from './meeting-assistant-flow.js'; +export { createMultiAgentNetworkExample } from './multi-agent-network.js'; +export { createPlanAndExecuteExample } from './plan-and-execute.js'; +export { createRaffleExample } from './raffle.js'; +export { createRagExample } from './rag.js'; +export { createReactAgentExample } from './react-agent.js'; +export { + createReactAgentFromScratch, + type ReactAgentMessage, + type ReactAgentModelResult, + type ReactTool, +} from './react-agent-from-scratch.js'; +export { createRewooExample } from './rewoo.js'; +export { createReflectionExample } from './reflection.js'; +export { createSelfEvaluationLoopFlowExample } from './self-evaluation-loop-flow.js'; +export { createSpecAgentLoopExample } from './spec-agent-loop.js'; +export { + createGuardrailedBugfixWorkflowExample, + createGuardrailedIncidentResponseExample, + createUnguardedIncidentResponseExample, +} from './workflow-guardrails.js'; +export { createSupervisorExample } from './supervisor.js'; +export { createWriteABookFlowExample } from './write-a-book-flow.js'; +export { createSqlAgentExample } from './sql-agent.js'; + +// Reference and concept examples +export { createAdapterExample } from './adapter.js'; +export { createBranchingExample } from './branching.js'; +export { createChatbotExample } from './chatbot.js'; +export { createChatbotMessagesExample } from './chatbot-messages.js'; +export { createClassifyExample } from './classify.js'; +export { createConditionalSubflowExample } from './conditional-subflow.js'; +export { createCustomerServiceSimExample } from './customer-service-sim.js'; +export { createDecideExample } from './decide.js'; +export { createEmailExample } from './email.js'; +export { createHitlExample } from './hitl.js'; +export { createJokeExample } from './joke.js'; +export { createJugsExample } from './jugs.js'; +export { createMapReduceExample } from './map-reduce.js'; +export { createNewspaperExample } from './newspaper.js'; +export { createRiverCrossingExample } from './river-crossing.js'; +export { createSimpleExample } from './simple.js'; +export { createSubflowExample } from './subflow.js'; +export { createToolCallingExample } from './tool-calling.js'; +export { createTutorExample } from './tutor.js'; diff --git a/examples/joke.ts b/examples/joke.ts index e9ca1cd..012bcf1 100644 --- a/examples/joke.ts +++ b/examples/joke.ts @@ -1,227 +1,99 @@ -import { assign, createActor, fromCallback, log, setup } from 'xstate'; -import { createAgent, fromDecision } from '../src'; -import { loadingAnimation } from './helpers/loader'; import { z } from 'zod'; -import { openai } from '@ai-sdk/openai'; -import { fromTerminal } from './helpers/helpers'; +import { createAgentMachine, type AgentAdapter } from '../src/index.js'; +import { + closePrompt, + createOpenAiGenerationAdapter, + formatResult, + isMain, + prompt, +} from './_run.js'; -export function getRandomFunnyPhrase() { - const funnyPhrases = [ - 'Concocting chuckles...', - 'Brewing belly laughs...', - 'Fabricating funnies...', - 'Assembling amusement...', - 'Molding merriment...', - 'Whipping up wisecracks...', - 'Generating guffaws...', - 'Inventing hilarity...', - 'Cultivating chortles...', - 'Hatching howlers...', - ]; - return funnyPhrases[Math.floor(Math.random() * funnyPhrases.length)]!; -} - -export function getRandomRatingPhrase() { - const ratingPhrases = [ - 'Assessing amusement...', - 'Evaluating hilarity...', - 'Ranking chuckles...', - 'Classifying cackles...', - 'Scoring snickers...', - 'Rating roars...', - 'Judging jollity...', - 'Measuring merriment...', - 'Rating rib-ticklers...', - ]; - return ratingPhrases[Math.floor(Math.random() * ratingPhrases.length)]!; -} - -const loader = fromCallback(({ input }: { input: string }) => { - const anim = loadingAnimation(input); - - return () => { - anim.stop(); - }; +const jokeSchema = z.object({ + joke: z.string(), }); -const agent = createAgent({ - name: 'joke-teller', - model: openai('gpt-4-turbo'), - events: { - askForTopic: z.object({ - topic: z.string().describe('The topic for the joke'), - }), - 'agent.tellJoke': z.object({ - joke: z.string().describe('The joke text'), - }), - 'agent.endJokes': z.object({}).describe('End the jokes'), - 'agent.rateJoke': z.object({ - rating: z.number().min(1).max(10), - explanation: z.string(), - }), - 'agent.continue': z.object({}).describe('Continue'), - 'agent.markRelevancy': z.object({ - relevant: z.boolean().describe('Whether the joke was relevant'), - explanation: z - .string() - .describe('The explanation for why the joke was relevant or not'), - }), - }, - context: { - topic: z.string().describe('The topic for the joke'), - jokes: z.array(z.string()).describe('The jokes told so far'), - desire: z.string().nullable().describe('The user desire'), - lastRating: z.number().nullable().describe('The last joke rating'), - loader: z.string().nullable().describe('The loader text'), - }, +const ratingSchema = z.object({ + rating: z.number().min(1).max(10), + explanation: z.string(), }); -const jokeMachine = setup({ - types: agent.types, - actors: { - agent: fromDecision(agent), - loader, - getFromTerminal: fromTerminal, - }, -}).createMachine({ - id: 'joke', - context: () => ({ - topic: '', - jokes: [], - desire: null, - lastRating: null, - loader: null, - }), - initial: 'waitingForTopic', - states: { - waitingForTopic: { - invoke: { - src: 'getFromTerminal', - input: 'Give me a joke topic.', - onDone: { - actions: assign({ - topic: ({ event }) => event.output, - }), - target: 'tellingJoke', - }, - }, +export function createJokeExample( + adapter: AgentAdapter = createOpenAiGenerationAdapter() +) { + return createAgentMachine({ + id: 'joke-example', + adapter, + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + topic: z.string(), + joke: z.string().nullable(), + rating: z.number().nullable(), + explanation: z.string().nullable(), + accepted: z.boolean(), + }), }, - tellingJoke: { - invoke: [ - { - src: 'agent', - input: ({ context }) => ({ - context: { - topic: context.topic, - }, - goal: `Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.`, - }), - }, - { - src: 'loader', - input: getRandomFunnyPhrase, - }, - ], - on: { - 'agent.tellJoke': { - actions: [ - assign({ - jokes: ({ context, event }) => [...context.jokes, event.joke], - }), - log(({ event }) => event.joke), - ], - target: 'relevance', - }, - }, - }, - relevance: { - invoke: { - src: 'agent', - input: ({ context }) => ({ - context: { - topic: context.topic, - lastJoke: context.jokes.at(-1), - }, - goal: 'An irrelevant joke has no reference to the topic. If the last joke is completely irrelevant to the topic, ask for a new joke topic. Otherwise, continue.', + context: (input) => ({ + topic: input.topic, + joke: null as string | null, + rating: null as number | null, + explanation: null as string | null, + accepted: false, + }), + initial: 'telling', + states: { + telling: { + schemas: { output: jokeSchema }, + system: 'You write short, clean jokes.', + prompt: ({ context }) => `Write one short joke about ${context.topic}.`, + onDone: ({ output }) => ({ + target: 'rating', + context: { joke: output.joke }, }), }, - on: { - 'agent.markRelevancy': [ - { - guard: ({ event }) => !event.relevant, - actions: log( - ({ event }) => 'Irrelevant joke: ' + event.explanation - ), - target: 'waitingForTopic', - description: 'Continue', - }, - { target: 'rateJoke' }, - ], - }, - }, - rateJoke: { - invoke: [ - { - src: 'agent', - input: ({ context }) => ({ - context: { - jokes: context.jokes, - }, - goal: `Rate the last joke on a scale of 1 to 10.`, - }), - }, - { - src: 'loader', - input: getRandomRatingPhrase, - }, - ], - on: { - 'agent.rateJoke': { - actions: [ - assign({ - lastRating: ({ event }) => event.rating, - }), - log( - ({ event }) => `Rating: ${event.rating}\n\n${event.explanation}` - ), - ], - target: 'decide', - }, - }, - }, - decide: { - invoke: { - src: 'agent', - input: ({ context }) => ({ + rating: { + schemas: { output: ratingSchema }, + system: 'You are a joke critic. Be fair and concise.', + prompt: ({ context }) => + [ + `Topic: ${context.topic}`, + `Joke: ${context.joke ?? ''}`, + '', + 'Rate the joke from 1 to 10 and explain briefly.', + ].join('\n'), + onDone: ({ output }) => ({ + target: 'done', context: { - lastRating: context.lastRating, + rating: output.rating, + explanation: output.explanation, + accepted: output.rating >= 7, }, - goal: `Choose what to do next, given the previous rating of the joke.`, }), }, - on: { - askForTopic: { - target: 'waitingForTopic', - actions: log("That joke wasn't good enough. Let's try again."), - description: - 'Ask for a new topic, because the last joke rated 6 or lower', - }, - 'agent.endJokes': { - target: 'end', - actions: log('That joke was good enough. Goodbye!'), - description: 'End the jokes, since the last joke rated 7 or higher', - }, + done: { + type: 'final', + output: ({ context }) => ({ + topic: context.topic, + joke: context.joke, + rating: context.rating, + explanation: context.explanation, + accepted: context.accepted, + }), }, }, - end: { - type: 'final', - }, - }, - exit: () => { - process.exit(); - }, -}); + }); +} -const actor = createActor(jokeMachine); +async function main() { + try { + const topic = await prompt('Joke topic'); + const machine = createJokeExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ topic })))); + } finally { + closePrompt(); + } +} -actor.start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/jugs.ts b/examples/jugs.ts new file mode 100644 index 0000000..dfce867 --- /dev/null +++ b/examples/jugs.ts @@ -0,0 +1,134 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { formatResult, isMain } from './_run.js'; + +const moveSchema = z.object({ + move: z + .enum(['fill5', 'pour5to3', 'empty3', 'done']) + .describe('The next move in the water jug puzzle'), + reasoning: z.string(), +}); + +const applySchema = z.object({ + jug3: z.number().int(), + jug5: z.number().int(), + step: z.string(), +}); + +function chooseWaterJugMove(jug3: number, jug5: number): z.infer { + const key = `${jug3},${jug5}`; + const plan: Record> = { + '0,0': { move: 'fill5', reasoning: 'Start by filling the larger jug.' }, + '0,5': { move: 'pour5to3', reasoning: 'Transfer water into the 3-gallon jug.' }, + '3,2': { move: 'empty3', reasoning: 'Empty the smaller jug to make room.' }, + '0,2': { move: 'pour5to3', reasoning: 'Move the remaining water into the 3-gallon jug.' }, + '2,0': { move: 'fill5', reasoning: 'Refill the 5-gallon jug.' }, + '2,5': { move: 'pour5to3', reasoning: 'Top off the 3-gallon jug to leave 4 gallons.' }, + '3,4': { move: 'done', reasoning: 'The 5-gallon jug now holds exactly 4 gallons.' }, + }; + + return plan[key] ?? { move: 'done', reasoning: 'No further move required.' }; +} + +function applyWaterJugMove( + jug3: number, + jug5: number, + move: z.infer['move'] +): z.infer { + switch (move) { + case 'fill5': + return { jug3, jug5: 5, step: 'Filled the 5-gallon jug.' }; + case 'pour5to3': { + const transfer = Math.min(3 - jug3, jug5); + return { + jug3: jug3 + transfer, + jug5: jug5 - transfer, + step: 'Poured from the 5-gallon jug into the 3-gallon jug.', + }; + } + case 'empty3': + return { jug3: 0, jug5, step: 'Emptied the 3-gallon jug.' }; + default: + return { jug3, jug5, step: 'Solved the puzzle.' }; + } +} + +export function createJugsExample() { + return createAgentMachine({ + id: 'jugs-example', + schemas: { + output: z.object({ + jug3: z.number(), + jug5: z.number(), + steps: z.array(z.string()), + reasoning: z.array(z.string()), + }), + }, + context: () => ({ + jug3: 0, + jug5: 0, + steps: [] as string[], + reasoning: [] as string[], + }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: moveSchema }, + invoke: async ({ context }) => chooseWaterJugMove(context.jug3, context.jug5), + onDone: ({ output, context }) => { + const nextReasoning = [...context.reasoning, output.reasoning]; + + if (output.move === 'done') { + return { + target: 'done' as const, + context: { reasoning: nextReasoning }, + }; + } + + return { + target: 'applying' as const, + input: { move: output.move }, + context: { reasoning: nextReasoning }, + }; + }, + }, + applying: { + schemas: { input: z.object({ + move: moveSchema.shape.move.exclude(['done']), + }), output: applySchema }, + invoke: async ({ context, input }) => + applyWaterJugMove( + context.jug3, + context.jug5, + input.move as 'fill5' | 'pour5to3' | 'empty3' + ), + onDone: ({ output, context }) => ({ + target: 'choosing', + context: { + jug3: output.jug3, + jug5: output.jug5, + steps: [...context.steps, output.step], + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + jug3: context.jug3, + jug5: context.jug5, + steps: context.steps, + reasoning: context.reasoning, + }), + }, + }, + }); +} + +async function main() { + const machine = createJugsExample(); + console.log(formatResult(await machine.execute(machine.getInitialState()))); +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/lead-score-flow.ts b/examples/lead-score-flow.ts new file mode 100644 index 0000000..c0c6879 --- /dev/null +++ b/examples/lead-score-flow.ts @@ -0,0 +1,209 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../src/index.js'; +import { + closePrompt, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; + +const leadSchema = z.object({ + id: z.string(), + company: z.string(), + contact: z.string(), +}); + +const scoredLeadSchema = leadSchema.extend({ + score: z.number().min(0).max(100), + rationale: z.string(), +}); + +const scoringSchema = z.object({ + scoredLeads: z.array(scoredLeadSchema), +}); + +const emailDraftSchema = z.object({ + leadId: z.string(), + draft: z.string(), +}); + +const emailBatchSchema = z.object({ + drafts: z.array(emailDraftSchema), +}); + +type Lead = z.infer; + +export function createLeadScoreFlowExample(options: { + scoreLeads?: (args: { + leads: Lead[]; + reviewNote: string | null; + }) => Promise>; + writeEmails?: (leads: z.infer[]) => Promise>; +} = {}) { + const scoreLeads = + options.scoreLeads ?? + (async ({ leads, reviewNote }) => ({ + scoredLeads: leads + .map((lead, index) => ({ + ...lead, + score: Math.max(0, 90 - index * 10 - (reviewNote ? 5 : 0)), + rationale: reviewNote + ? `Adjusted after review: ${reviewNote}` + : `Initial score for ${lead.company}`, + })) + .sort((a, b) => b.score - a.score), + })); + + const writeEmails = + options.writeEmails ?? + (async (leads) => ({ + drafts: leads.map((lead) => ({ + leadId: lead.id, + draft: `Hi ${lead.contact}, I would love to talk about ${lead.company}.`, + })), + })); + + return createAgentMachine({ + id: 'lead-score-flow-example', + schemas: { + input: z.object({ + leads: z.array(leadSchema), + }), + output: z.object({ + scoredLeads: z.array(scoredLeadSchema), + topLeads: z.array(scoredLeadSchema), + emailDrafts: z.array(emailDraftSchema), + reviewCount: z.number(), + }), + events: { + 'review.approve': z.object({}), + 'review.requestChanges': z.object({ + note: z.string(), + }), + }, + }, + context: (input) => ({ + leads: input.leads, + scoredLeads: [] as z.infer[], + topLeads: [] as z.infer[], + emailDrafts: [] as z.infer[], + reviewNote: null as string | null, + reviewCount: 0, + }), + initial: 'scoring', + states: { + scoring: { + schemas: { output: scoringSchema }, + invoke: async ({ context }) => + scoreLeads({ + leads: context.leads, + reviewNote: context.reviewNote, + }), + onDone: ({ output, context }) => ({ + target: 'reviewing', + context: { + scoredLeads: output.scoredLeads, + topLeads: output.scoredLeads.slice(0, 3), + reviewNote: null, + reviewCount: context.reviewCount + 1, + }, + }), + }, + reviewing: { + on: { + 'review.approve': { + target: 'writing', + }, + 'review.requestChanges': ({ event }) => ({ + target: 'scoring', + context: { + reviewNote: event.note, + }, + }), + }, + }, + writing: { + schemas: { output: emailBatchSchema }, + invoke: async ({ context }) => writeEmails(context.scoredLeads), + onDone: ({ output }) => ({ + target: 'done', + context: { + emailDrafts: output.drafts, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + scoredLeads: context.scoredLeads, + topLeads: context.topLeads, + emailDrafts: context.emailDrafts, + reviewCount: context.reviewCount, + }), + }, + }, + }); +} + +async function main() { + try { + const companies = (await prompt('Comma-separated company names')) + .split(',') + .map((value) => value.trim()) + .filter(Boolean); + const machine = createLeadScoreFlowExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { + leads: companies.map((company, index) => ({ + id: `lead-${index + 1}`, + company, + contact: `Contact ${index + 1}`, + })), + }, + }); + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + output: snapshot.output, + }); + break; + } + + if (snapshot.value === 'reviewing') { + console.log(snapshot.context.topLeads); + const answer = await prompt('Type /approve or provide a review note'); + await run.send( + answer === '/approve' + ? { type: 'review.approve' } + : { + type: 'review.requestChanges', + note: answer, + } + ); + continue; + } + + throw new Error('Lead score flow entered an unexpected pending state.'); + } + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/map-reduce.ts b/examples/map-reduce.ts new file mode 100644 index 0000000..25abe38 --- /dev/null +++ b/examples/map-reduce.ts @@ -0,0 +1,125 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + generateExampleText, + isMain, + prompt, +} from './_run.js'; + +const subjectsSchema = z.object({ + subjects: z.array(z.string()), +}); + +const jokesSchema = z.object({ + jokes: z.array(z.string()), +}); + +const bestJokeSchema = z.object({ + bestJoke: z.string(), +}); + +export function createMapReduceExample( + options: { + planSubjects?: (topic: string) => Promise>; + writeJoke?: (subject: string) => Promise; + chooseBest?: (jokes: string[]) => Promise>; + } = {} +) { + return createAgentMachine({ + id: 'map-reduce-example', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + subjects: z.array(z.string()), + jokes: z.array(z.string()), + bestJoke: z.string().nullable(), + }), + }, + context: (input) => ({ + topic: input.topic, + subjects: [] as string[], + jokes: [] as string[], + bestJoke: null as string | null, + }), + initial: 'planning', + states: { + planning: { + schemas: { output: subjectsSchema }, + invoke: async ({ context }) => + (options.planSubjects + ?? ((topic) => + generateExampleObject({ + schema: subjectsSchema, + system: 'You break a topic into a few concrete subtopics.', + prompt: `List 2 to 4 specific subtopics worth covering for: ${topic}`, + })))(context.topic), + onDone: ({ output }) => ({ + target: 'mapping', + context: { subjects: output.subjects }, + }), + }, + mapping: { + schemas: { output: jokesSchema }, + invoke: async ({ context }) => { + const jokes = await Promise.all( + context.subjects.map((subject) => + (options.writeJoke + ?? ((value) => + generateExampleText({ + system: 'You write one-line jokes.', + prompt: `Write one short joke about ${value}.`, + })))(subject) + ) + ); + + return { jokes }; + }, + onDone: ({ output }) => ({ + target: 'reducing', + context: { jokes: output.jokes }, + }), + }, + reducing: { + schemas: { output: bestJokeSchema }, + invoke: async ({ context }) => + (options.chooseBest + ?? ((jokes) => + generateExampleObject({ + schema: bestJokeSchema, + system: 'You pick the strongest joke from a list.', + prompt: ['Choose the best joke from this list:', ...jokes].join('\n'), + })))(context.jokes), + onDone: ({ output }) => ({ + target: 'done', + context: { bestJoke: output.bestJoke }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + subjects: context.subjects, + jokes: context.jokes, + bestJoke: context.bestJoke, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const machine = createMapReduceExample(); + const result = await machine.execute(machine.getInitialState({ topic })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/meeting-assistant-flow.ts b/examples/meeting-assistant-flow.ts new file mode 100644 index 0000000..ae8171e --- /dev/null +++ b/examples/meeting-assistant-flow.ts @@ -0,0 +1,152 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const taskSchema = z.object({ + title: z.string(), + owner: z.string(), +}); + +const extractionSchema = z.object({ + summary: z.string(), + tasks: z.array(taskSchema), +}); + +const fanOutSchema = z.object({ + trelloCardIds: z.array(z.string()), + csvPath: z.string(), + slackMessageId: z.string(), +}); + +export function createMeetingAssistantFlowExample(options: { + extractTasks?: (notes: string) => Promise>; + addTasksToTrello?: (tasks: z.infer[]) => Promise<{ trelloCardIds: string[] }>; + saveTasksToCsv?: (tasks: z.infer[]) => Promise<{ csvPath: string }>; + sendSlackNotification?: (args: { + summary: string; + tasks: z.infer[]; + }) => Promise<{ slackMessageId: string }>; +} = {}) { + const extractTasks = + options.extractTasks ?? + ((notes: string) => + generateExampleObject({ + schema: extractionSchema, + system: 'Extract a concise meeting summary and explicit action items.', + prompt: notes, + })); + + const addTasksToTrello = + options.addTasksToTrello ?? + (async (tasks) => ({ + trelloCardIds: tasks.map((_, index) => `card-${index + 1}`), + })); + + const saveTasksToCsv = + options.saveTasksToCsv ?? + (async () => ({ + csvPath: 'new_tasks.csv', + })); + + const sendSlackNotification = + options.sendSlackNotification ?? + (async () => ({ + slackMessageId: 'slack-message-1', + })); + + return createAgentMachine({ + id: 'meeting-assistant-flow-example', + schemas: { + input: z.object({ + notes: z.string(), + }), + output: z.object({ + summary: z.string().nullable(), + tasks: z.array(taskSchema), + trelloCardIds: z.array(z.string()), + csvPath: z.string().nullable(), + slackMessageId: z.string().nullable(), + }), + }, + context: (input) => ({ + notes: input.notes, + summary: null as string | null, + tasks: [] as z.infer[], + trelloCardIds: [] as string[], + csvPath: null as string | null, + slackMessageId: null as string | null, + }), + initial: 'extracting', + states: { + extracting: { + schemas: { output: extractionSchema }, + invoke: async ({ context }) => extractTasks(context.notes), + onDone: ({ output }) => ({ + target: 'dispatching', + context: { + summary: output.summary, + tasks: output.tasks, + }, + }), + }, + dispatching: { + schemas: { output: fanOutSchema }, + invoke: async ({ context }) => { + const [trello, csv, slack] = await Promise.all([ + addTasksToTrello(context.tasks), + saveTasksToCsv(context.tasks), + sendSlackNotification({ + summary: context.summary ?? '', + tasks: context.tasks, + }), + ]); + + return { + trelloCardIds: trello.trelloCardIds, + csvPath: csv.csvPath, + slackMessageId: slack.slackMessageId, + }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { + trelloCardIds: output.trelloCardIds, + csvPath: output.csvPath, + slackMessageId: output.slackMessageId, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + summary: context.summary, + tasks: context.tasks, + trelloCardIds: context.trelloCardIds, + csvPath: context.csvPath, + slackMessageId: context.slackMessageId, + }), + }, + }, + }); +} + +async function main() { + try { + const notes = await prompt('Meeting notes'); + const machine = createMeetingAssistantFlowExample(); + const result = await machine.execute(machine.getInitialState({ notes })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/multi-agent-network.ts b/examples/multi-agent-network.ts new file mode 100644 index 0000000..da5533d --- /dev/null +++ b/examples/multi-agent-network.ts @@ -0,0 +1,332 @@ +import { z } from 'zod'; +import { + createAgentMachine, + decide, + decideResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const researchParamsSchema = z.object({ + focus: z.string(), +}); + +const writeParamsSchema = z.object({ + angle: z.string(), +}); + +const researchNotesSchema = z.object({ + notes: z.array(z.string()).min(2).max(5), +}); + +const researchHandoffSchema = z.object({ + notes: z.array(z.string()).min(2).max(5), + handoff: z.string(), +}); + +const draftSchema = z.object({ + draft: z.string(), +}); + +const draftHandoffSchema = z.object({ + draft: z.string(), + handoff: z.string(), +}); + +export function createMultiAgentNetworkExample( + options: { + adapter?: DecideAdapter; + research?: (args: { + topic: string; + focus: string; + }) => Promise>; + write?: (args: { + topic: string; + notes: string[]; + angle: string; + }) => Promise>; + } = {} +) { + const coordinatorOptions = { + research: { + description: 'Send the task to the research specialist.', + schema: researchParamsSchema, + }, + write: { + description: 'Send the task to the writing specialist.', + schema: writeParamsSchema, + }, + finalize: { + description: 'Stop the network and return the current result.', + }, + } as const; + + const adapter = options.adapter ?? createOpenAiDecisionAdapter(); + + const research = + options.research ?? + ((args: { topic: string; focus: string }) => + generateExampleObject({ + schema: researchNotesSchema, + system: 'You are a research specialist. Return concise notes only.', + prompt: [ + `Topic: ${args.topic}`, + `Focus: ${args.focus}`, + '', + 'Return 2 to 5 concise research notes that help another specialist continue the task.', + ].join('\n'), + })); + + const write = + options.write ?? + ((args: { topic: string; notes: string[]; angle: string }) => + generateExampleObject({ + schema: draftSchema, + system: 'You are a writing specialist. Turn notes into a concise draft.', + prompt: [ + `Topic: ${args.topic}`, + `Angle: ${args.angle}`, + '', + 'Notes:', + ...args.notes.map((note) => `- ${note}`), + '', + 'Write a short specialist draft.', + ].join('\n'), + })); + + const researchAgent = createAgentMachine({ + id: 'network-research-agent', + schemas: { + input: z.object({ + topic: z.string(), + focus: z.string(), + }), + output: z.object({ + notes: z.array(z.string()), + }), + }, + context: (input) => ({ + topic: input.topic, + focus: input.focus, + notes: [] as string[], + }), + initial: 'researching', + states: { + researching: { + schemas: { output: researchNotesSchema }, + invoke: async ({ context }) => + research({ + topic: context.topic, + focus: context.focus, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { notes: output.notes }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + notes: context.notes, + }), + }, + }, + }); + + const writerAgent = createAgentMachine({ + id: 'network-writer-agent', + schemas: { + input: z.object({ + topic: z.string(), + notes: z.array(z.string()), + angle: z.string(), + }), + output: z.object({ + draft: z.string(), + }), + }, + context: (input) => ({ + topic: input.topic, + notes: input.notes, + angle: input.angle, + draft: null as string | null, + }), + initial: 'writing', + states: { + writing: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => + write({ + topic: context.topic, + notes: context.notes, + angle: context.angle, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + draft: context.draft ?? '', + }), + }, + }, + }); + + return createAgentMachine({ + id: 'multi-agent-network-example', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + topic: z.string(), + notes: z.array(z.string()), + draft: z.string().nullable(), + handoffs: z.array(z.string()), + }), + }, + context: (input) => ({ + topic: input.topic, + notes: [] as string[], + draft: null as string | null, + handoffs: [] as string[], + }), + initial: 'coordinating', + states: { + coordinating: { + schemas: { output: decideResultSchema(coordinatorOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'You are a coordinator deciding which specialist should act next.', + 'Route to research when the task needs more facts.', + 'Route to writing when there are enough notes to draft.', + 'Finalize only when a usable draft already exists.', + '', + `Topic: ${context.topic}`, + context.notes.length + ? `Notes:\n${context.notes.map((note) => `- ${note}`).join('\n')}` + : 'Notes: none yet', + context.draft ? `Current draft:\n${context.draft}` : 'Current draft: none yet', + context.handoffs.length + ? `Prior handoffs:\n${context.handoffs.map((handoff, index) => `${index + 1}. ${handoff}`).join('\n')}` + : 'Prior handoffs: none', + ].join('\n'), + options: coordinatorOptions, + }), + onDone: ({ output }) => { + if (output.choice === 'research') { + return { + target: 'researching', + input: { + focus: output.data.focus ?? 'gather the most useful supporting facts', + }, + }; + } + + if (output.choice === 'write') { + return { + target: 'writing', + input: { + angle: output.data.angle ?? 'produce the clearest concise draft', + }, + }; + } + + return { + target: 'done', + }; + }, + }, + researching: { + schemas: { input: researchParamsSchema, output: researchHandoffSchema }, + invoke: async ({ context, input }) => { + const result = await researchAgent.execute( + researchAgent.getInitialState({ + topic: context.topic, + focus: input.focus, + }) + ); + + if (result.status !== 'done') { + throw new Error('Research agent did not finish'); + } + + return { + notes: result.output.notes, + handoff: `researcher:${input.focus}`, + }; + }, + onDone: ({ output, context }) => ({ + target: 'coordinating', + context: { + notes: output.notes, + handoffs: [...context.handoffs, output.handoff], + }, + }), + }, + writing: { + schemas: { input: writeParamsSchema, output: draftHandoffSchema }, + invoke: async ({ context, input }) => { + const result = await writerAgent.execute( + writerAgent.getInitialState({ + topic: context.topic, + notes: context.notes, + angle: input.angle, + }) + ); + + if (result.status !== 'done') { + throw new Error('Writer agent did not finish'); + } + + return { + draft: result.output.draft, + handoff: `writer:${input.angle}`, + }; + }, + onDone: ({ output, context }) => ({ + target: 'coordinating', + context: { + draft: output.draft, + handoffs: [...context.handoffs, output.handoff], + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + topic: context.topic, + notes: context.notes, + draft: context.draft, + handoffs: context.handoffs, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const machine = createMultiAgentNetworkExample(); + const result = await machine.execute(machine.getInitialState({ topic })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/newspaper.ts b/examples/newspaper.ts index 3671a25..7734598 100644 --- a/examples/newspaper.ts +++ b/examples/newspaper.ts @@ -1,324 +1,186 @@ -// Based on GPT Newspaper: -// https://github.com/assafelovic/gpt-newspaper -// https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; -import { TavilySearchAPIRetriever } from '@langchain/community/retrievers/tavily_search_api'; -import { ChatOpenAI } from '@langchain/openai'; -import { HumanMessage, SystemMessage } from '@langchain/core/messages'; -import { assign, createActor, fromPromise, setup } from 'xstate'; - -interface AgentState { - topic: string; - searchResults?: string; - article?: string; - critique?: string; - revisionCount: number; -} - -function model() { - return new ChatOpenAI({ - temperature: 0, - modelName: 'gpt-4-1106-preview', - openAIApiKey: process.env.OPENAI_API_KEY, - }); -} - -async function search({ topic }: Pick): Promise { - const retriever = new TavilySearchAPIRetriever({ - k: 10, - apiKey: process.env.TAVILY_API_KEY, - }); - // let topic = state.agentState.topic; - // must be at least 5 characters long - if (topic.length < 5) { - topic = 'topic: ' + topic; - } - const docs = await retriever.invoke(topic); - return JSON.stringify(docs); -} - -async function curate( - input: Pick -): Promise { - const response = await model().invoke( - [ - new SystemMessage( - `You are a personal newspaper editor. - Your sole task is to return a list of URLs of the 5 most relevant articles for the provided topic or query as a JSON list of strings - in this format: - { - urls: ["url1", "url2", "url3", "url4", "url5"] - } - .`.replace(/\s+/g, ' ') - ), - new HumanMessage( - `Today's date is ${new Date().toLocaleDateString('en-GB')}. - Topic or Query: ${input.topic} - - Here is a list of articles: - ${input.searchResults}`.replace(/\s+/g, ' ') - ), - ], - { - response_format: { - type: 'json_object', - }, - } - ); - const urls = JSON.parse(response.content as string).urls; - const searchResults = JSON.parse(input.searchResults!); - const newSearchResults = searchResults.filter((result: any) => { - return urls.includes(result.metadata.source); - }); - return JSON.stringify(newSearchResults); -} +const searchSchema = z.object({ + searchResults: z.array(z.string()), +}); -async function critique( - input: Pick -): Promise { - let feedbackInstructions = ''; - if (input.critique) { - feedbackInstructions = - `The writer has revised the article based on your previous critique: ${input.critique} - The writer might have left feedback for you encoded between tags. - The feedback is only for you to see and will be removed from the final article. - `.replace(/\s+/g, ' '); - } - const response = await model().invoke([ - new SystemMessage( - `You are a personal newspaper writing critique. Your sole purpose is to provide short feedback on a written - article so the writer will know what to fix. - Today's date is ${new Date().toLocaleDateString('en-GB')} - Your task is to provide a really short feedback on the article only if necessary. - if you think the article is good, please return [DONE]. - you can provide feedback on the revised article or just - return [DONE] if you think the article is good. - Please return a string of your critique or [DONE].`.replace(/\s+/g, ' ') - ), - new HumanMessage( - `${feedbackInstructions} - This is the article: ${input.article}` - ), - ]); - const content = response.content as string; - console.log('critique:', content); - return content.includes('[DONE]') ? undefined : content; -} +const articleSchema = z.object({ + article: z.string(), +}); -async function write( - input: Pick -): Promise { - const response = await model().invoke([ - new SystemMessage( - `You are a personal newspaper writer. Your sole purpose is to write a well-written article about a - topic using a list of articles. Write 5 paragraphs in markdown.`.replace( - /\s+/g, - ' ' - ) - ), - new HumanMessage( - `Today's date is ${new Date().toLocaleDateString('en-GB')}. - Your task is to write a critically acclaimed article for me about the provided query or - topic based on the sources. - Here is a list of articles: ${input.searchResults} - This is the topic: ${input.topic} - Please return a well-written article based on the provided information.`.replace( - /\s+/g, - ' ' - ) - ), - ]); - const content = response.content as string; - return content; -} +const critiqueSchema = z.object({ + critique: z.string().nullable(), +}); -async function revise( - input: Pick -): Promise { - const response = await model().invoke([ - new SystemMessage( - `You are a personal newspaper editor. Your sole purpose is to edit a well-written article about a - topic based on given critique.`.replace(/\s+/g, ' ') - ), - new HumanMessage( - `Your task is to edit the article based on the critique given. - This is the article: ${input.article} - This is the critique: ${input.critique} - Please return the edited article based on the critique given. - You may leave feedback about the critique encoded between tags like this: - here goes the feedback ...`.replace(/\s+/g, ' ') - ), - ]); - const content = response.content as string; - return content; -} +export function createNewspaperExample( + options: { + search?: (topic: string) => Promise>; + curate?: (topic: string, searchResults: string[]) => Promise>; + write?: (topic: string, searchResults: string[]) => Promise>; + critique?: (article: string, revisionCount: number) => Promise>; + revise?: (article: string, critique: string) => Promise>; + maxRevisions?: number; + } = {} +) { + const search = + options.search ?? + ((topic: string) => + generateExampleObject({ + schema: searchSchema, + system: 'You brainstorm plausible research leads for an article topic.', + prompt: `List 3 to 5 concise research leads or search angles for an article about ${topic}.`, + })); + const curate = + options.curate ?? + ((topic: string, searchResults: string[]) => + generateExampleObject({ + schema: searchSchema, + system: 'You curate research inputs for a focused article.', + prompt: [ + `Topic: ${topic}`, + 'Choose the best 2 or 3 research leads from the list below.', + ...searchResults.map((result) => `- ${result}`), + ].join('\n'), + })); + const write = + options.write ?? + ((topic: string, searchResults: string[]) => + generateExampleObject({ + schema: articleSchema, + system: 'You write short newspaper-style drafts in Markdown.', + prompt: [ + `Topic: ${topic}`, + 'Write a short article draft using these research leads:', + ...searchResults.map((result) => `- ${result}`), + ].join('\n'), + })); + const critique = + options.critique ?? + ((article: string, revisionCount: number) => + generateExampleObject({ + schema: critiqueSchema, + system: 'You critique article drafts. Return null when no further revision is needed.', + prompt: [ + `Revision count: ${revisionCount}`, + 'Review this article draft and either return one concise critique or null if it is ready.', + '', + article, + ].join('\n'), + })); + const revise = + options.revise ?? + ((article: string, notes: string) => + generateExampleObject({ + schema: articleSchema, + system: 'You revise article drafts while preserving the main facts.', + prompt: [ + 'Revise the article to address this critique:', + notes, + '', + article, + ].join('\n'), + })); -const machine = setup({ - types: { - context: {} as AgentState, - }, - actors: { - search: fromPromise(({ input }: { input: Pick }) => { - return search(input); + return createAgentMachine({ + id: 'newspaper-example', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + topic: z.string(), + article: z.string().nullable(), + revisionCount: z.number(), + searchResults: z.array(z.string()), + }), + }, + context: (input) => ({ + topic: input.topic, + searchResults: [] as string[], + article: null as string | null, + critique: null as string | null, + revisionCount: 0, + maxRevisions: options.maxRevisions ?? 2, }), - curate: fromPromise( - ({ input }: { input: Pick }) => { - return curate(input); - } - ), - critique: fromPromise( - ({ input }: { input: Pick }) => { - return critique(input); - } - ), - write: fromPromise( - ({ input }: { input: Pick }) => { - return write(input); - } - ), - revise: fromPromise( - ({ input }: { input: Pick }) => { - return revise(input); - } - ), - }, -}).createMachine({ - context: { - topic: 'Orlando', - revisionCount: 0, - }, - initial: 'search', - states: { - search: { - invoke: { - src: 'search', - input: ({ context }) => ({ - topic: context.topic, + initial: 'searching', + states: { + searching: { + schemas: { output: searchSchema }, + invoke: async ({ context }) => search(context.topic), + onDone: ({ output }) => ({ + target: 'curating', + context: { searchResults: output.searchResults }, }), - onDone: { - actions: assign({ - searchResults: ({ event }) => event.output, - }), - target: 'curate', - }, }, - }, - curate: { - invoke: { - src: 'curate', - input: ({ context }) => ({ - topic: context.topic, - searchResults: context.searchResults!, + curating: { + schemas: { output: searchSchema }, + invoke: async ({ context }) => curate(context.topic, context.searchResults), + onDone: ({ output }) => ({ + target: 'writing', + context: { searchResults: output.searchResults }, }), - onDone: { - actions: assign({ - searchResults: ({ event }) => event.output, - }), - target: 'write', - }, }, - }, - write: { - invoke: { - src: 'write', - input: ({ context }) => ({ - topic: context.topic, - searchResults: context.searchResults!, + writing: { + schemas: { output: articleSchema }, + invoke: async ({ context }) => write(context.topic, context.searchResults), + onDone: ({ output }) => ({ + target: 'critiquing', + context: { article: output.article }, }), - onDone: { - actions: assign({ - article: ({ event }) => event.output, - }), - target: 'critique', - }, }, - }, - critique: { - invoke: { - src: 'critique', - input: ({ context }) => ({ - article: context.article!, - critique: context.critique, + critiquing: { + schemas: { output: critiqueSchema }, + invoke: async ({ context }) => + critique(context.article ?? '', context.revisionCount), + onDone: ({ output, context }) => ({ + target: + !output.critique || context.revisionCount >= context.maxRevisions + ? 'done' + : 'revising', + context: { critique: output.critique }, }), - onDone: [ - { - guard: ({ event }) => event.output === undefined, - target: 'done', - }, - { - actions: assign({ - article: ({ event }) => event.output, - }), - target: 'revise', - }, - ], }, - }, - revise: { - always: { - guard: ({ context }) => context.revisionCount > 3, - target: 'done', + revising: { + schemas: { output: articleSchema }, + invoke: async ({ context }) => + revise(context.article ?? '', context.critique ?? ''), + onDone: ({ output, context }) => ({ + target: 'critiquing', + context: { + article: output.article, + revisionCount: context.revisionCount + 1, + }, + }), }, - entry: assign({ - revisionCount: ({ context }) => context.revisionCount + 1, - }), - invoke: { - src: 'revise', - input: ({ context }) => ({ - article: context.article!, - critique: context.critique, + done: { + type: 'final', + output: ({ context }) => ({ + topic: context.topic, + article: context.article, + revisionCount: context.revisionCount, + searchResults: context.searchResults, }), - onDone: { - actions: assign({ - article: ({ event }) => event.output, - }), - target: 'revise', - reenter: true, - }, }, }, - done: { - type: 'final', - }, - }, - output: ({ context }) => context.article, -}); - -const actor = createActor(machine, { - // inspect: (inspEv) => { - // if (inspEv.type === '@xstate.event') { - // console.log(JSON.stringify(inspEv.event, null, 2)); - // } - // }, -}); - -actor.subscribe({ - next: (s) => { - console.log('State:', s.value); - console.log( - 'Context:', - JSON.stringify( - s.context, - (k, v) => { - if (typeof v === 'string') { - // truncate if longer than 50 chars - return v.length > 50 ? `${v.slice(0, 50)}...` : v; - } - return v; - }, - 2 - ) - ); - }, - complete: () => { - console.log(actor.getSnapshot().output); - }, - error: (err) => { - console.error(err); - }, -}); + }); +} -actor.start(); +async function main() { + try { + const topic = await prompt('Newspaper topic'); + const machine = createNewspaperExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ topic })))); + } finally { + closePrompt(); + } +} -// keep the process alive by invoking a promise that never resolves -new Promise(() => {}); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/next-ai-sdk-ui.ts b/examples/next-ai-sdk-ui.ts new file mode 100644 index 0000000..ccc2a31 --- /dev/null +++ b/examples/next-ai-sdk-ui.ts @@ -0,0 +1,214 @@ +import { + convertToModelMessages, + createUIMessageStream, + createUIMessageStreamResponse, + streamText, + type UIMessage, +} from 'ai'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../src/index.js'; +import { createExampleModel } from './_run.js'; + +const uiMessagesSchema = z.object({ + messages: z.array(z.custom()), +}); + +const streamedTextSchema = z.object({ + text: z.string(), +}); + +const notificationSchema = z.object({ + message: z.string(), + level: z.enum(['info', 'warning', 'error']), +}); + +const sourceSchema = z.object({ + id: z.string(), + url: z.string().url(), + title: z.string(), +}); + +export type AgentUiMessage = UIMessage< + unknown, + { + notification: z.infer; + } +>; + +export function createNextAiSdkUiRoute(options: { + streamReply?: (args: { + messages: UIMessage[]; + onDelta: (delta: string) => void; + }) => Promise>; +} = {}) { + const streamReply = + options.streamReply ?? + (async ({ + messages, + onDelta, + }: { + messages: UIMessage[]; + onDelta: (delta: string) => void; + }) => { + const result = streamText({ + model: createExampleModel('openai/gpt-5.4-nano'), + messages: await convertToModelMessages(messages), + }); + + for await (const delta of result.textStream) { + onDelta(delta); + } + + return { + text: await result.text, + }; + }); + + const machine = createAgentMachine({ + id: 'next-ai-sdk-ui-example', + schemas: { + input: uiMessagesSchema, + output: streamedTextSchema, + emitted: { + notification: notificationSchema, + source: sourceSchema, + textPart: z.object({ + delta: z.string(), + }), + }, + events: { + begin: z.object({}), + }, + }, + context: (input) => ({ + messages: input.messages, + finalText: '', + }), + initial: 'ready', + states: { + ready: { + on: { + begin: { + target: 'drafting', + }, + }, + }, + drafting: { + schemas: { output: streamedTextSchema }, + invoke: async ({ context }, enq) => { + enq.emit({ + type: 'notification', + message: 'Drafting reply...', + level: 'info', + }); + enq.emit({ + type: 'source', + id: 'agent-docs', + url: 'https://stately.ai/docs/agents', + title: 'Stately Agent documentation', + }); + + return streamReply({ + messages: context.messages, + onDelta: (delta) => { + enq.emit({ + type: 'textPart', + delta, + }); + }, + }); + }, + onDone: ({ output }) => ({ + target: 'done', + context: { + finalText: output.text, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + text: context.finalText, + }), + }, + }, + }); + + return { + async POST(request: Request): Promise { + const { messages } = uiMessagesSchema.parse(await request.json()); + + const stream = createUIMessageStream({ + originalMessages: messages, + execute: async ({ writer }) => { + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { messages }, + }); + + const textId = 'assistant-response'; + let textStarted = false; + + const offNotification = run.on('notification', (event) => { + writer.write({ + type: 'data-notification', + data: { + message: event.message, + level: event.level, + }, + transient: true, + }); + }); + const offSource = run.on('source', (event) => { + writer.write(({ + type: 'source', + value: { + type: 'source', + sourceType: 'url', + id: event.id, + url: event.url, + title: event.title, + }, + } as unknown) as never); + }); + const offTextPart = run.on('textPart', (event) => { + if (!textStarted) { + writer.write({ + type: 'text-start', + id: textId, + }); + textStarted = true; + } + + writer.write({ + type: 'text-delta', + id: textId, + delta: event.delta, + }); + }); + + try { + await run.send({ type: 'begin' }); + } finally { + offNotification(); + offSource(); + offTextPart(); + } + + if (textStarted) { + writer.write({ + type: 'text-end', + id: textId, + }); + } + }, + }); + + return createUIMessageStreamResponse({ stream }); + }, + }; +} diff --git a/examples/next-app-router.ts b/examples/next-app-router.ts new file mode 100644 index 0000000..5a9e826 --- /dev/null +++ b/examples/next-app-router.ts @@ -0,0 +1,120 @@ +import { type RunStore } from '../src/index.js'; +import type { NextRouteContext } from '../src/next/index.js'; +export { + dynamic, + maxDuration, + runtime, +} from '../src/next/index.js'; +export type { NextRouteContext } from '../src/next/index.js'; +import { + createPersistenceSessionHttpHandler, + type SessionHttpHandlerOptions, +} from './http-session.js'; +import { + createStreamingSessionHttpController, + type StreamingSessionHttpController, +} from './http-streaming-session.js'; + +export interface NextReviewRouteHandlers { + sessions: { + POST(request: Request): Promise; + }; + session: { + GET( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + events: { + POST( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; +} + +export interface NextStreamingRouteHandlers { + sessions: { + POST(request: Request): Promise; + }; + session: { + GET( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + stream: { + GET( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + advance(streamId: string): void; + dropActiveSession(sessionId: string): void; +} + +export function createNextReviewRouteHandlers( + options: SessionHttpHandlerOptions = {} +): NextReviewRouteHandlers { + const handle = createPersistenceSessionHttpHandler(options); + + return { + sessions: { + POST(request) { + return handle(rewritePath(request, '/sessions')); + }, + }, + session: { + async GET(request, context) { + const { sessionId } = await context.params; + return handle(rewritePath(request, `/sessions/${sessionId}`)); + }, + }, + events: { + async POST(request, context) { + const { sessionId } = await context.params; + return handle(rewritePath(request, `/sessions/${sessionId}/events`)); + }, + }, + }; +} + +export function createNextStreamingRouteHandlers(options: { + store?: RunStore; +} = {}): NextStreamingRouteHandlers { + const controller = createStreamingSessionHttpController(options); + + return { + sessions: { + POST(request) { + return controller.handle(rewritePath(request, '/sessions')); + }, + }, + session: { + async GET(request, context) { + const { sessionId } = await context.params; + return controller.handle(rewritePath(request, `/sessions/${sessionId}`)); + }, + }, + stream: { + async GET(request, context) { + const { sessionId } = await context.params; + return controller.handle( + rewritePath(request, `/sessions/${sessionId}/stream`) + ); + }, + }, + advance(streamId) { + controller.advance(streamId); + }, + dropActiveSession(sessionId) { + controller.dropActiveSession(sessionId); + }, + }; +} + +function rewritePath(request: Request, pathname: string): Request { + const url = new URL(request.url); + url.pathname = pathname; + return new Request(url, request); +} diff --git a/examples/persistence.ts b/examples/persistence.ts new file mode 100644 index 0000000..f43c0db --- /dev/null +++ b/examples/persistence.ts @@ -0,0 +1,132 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + restoreSession, + startSession, +} from '../src/index.js'; +import { + closePrompt, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const summarySchema = z.object({ + summary: z.string(), +}); + +export function createPersistenceExample( + summarize: (args: { + request: string; + approved: boolean; + }) => Promise> = async (args) => + generateExampleObject({ + schema: summarySchema, + system: 'You summarize approved requests in one concise sentence.', + prompt: [ + `Request: ${args.request}`, + `Approved: ${String(args.approved)}`, + '', + 'Write a short summary.', + ].join('\n'), + }) +) { + return createAgentMachine({ + id: 'persistence-example', + schemas: { + input: z.object({ + request: z.string(), + }), + output: z.object({ + request: z.string(), + approved: z.boolean(), + summary: z.string().nullable(), + }), + events: { + approve: z.object({}), + }, + }, + context: (input) => ({ + request: input.request, + approved: false, + summary: null as string | null, + }), + initial: 'review', + states: { + review: { + on: { + approve: { + target: 'summarizing', + context: { approved: true }, + }, + }, + }, + summarizing: { + schemas: { output: summarySchema }, + invoke: async ({ context }) => + summarize({ + request: context.request, + approved: context.approved, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { summary: output.summary }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + request: context.request, + approved: context.approved, + summary: context.summary, + }), + }, + }, + }); +} + +export async function runPersistenceExample( + input: { request: string }, + options: { + summarize?: (args: { + request: string; + approved: boolean; + }) => Promise>; + } = {} +) { + const machine = createPersistenceExample(options.summarize); + const store = createMemoryRunStore(); + + const liveRun = await startSession(machine, { + store, + input, + }); + + await liveRun.send({ type: 'approve' }); + + const restoredRun = await restoreSession(machine, { + sessionId: liveRun.sessionId, + store, + }); + + return { + sessionId: liveRun.sessionId, + liveSnapshot: liveRun.getSnapshot(), + restoredSnapshot: restoredRun.getSnapshot(), + }; +} + +async function main() { + try { + const request = await prompt('Request'); + const result = await runPersistenceExample({ request }); + console.log(result); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/persistent-multi-agent-network.ts b/examples/persistent-multi-agent-network.ts new file mode 100644 index 0000000..f3f12b6 --- /dev/null +++ b/examples/persistent-multi-agent-network.ts @@ -0,0 +1,111 @@ +import { + createMemoryRunStore, + restoreSession, + startSession, + type PersistedSnapshot, +} from '../src/index.js'; +import { createMultiAgentNetworkExample } from './multi-agent-network.js'; + +type NetworkOptions = Parameters[0]; + +export function createPersistentMultiAgentNetworkExample( + options: NetworkOptions = {} +) { + return createMultiAgentNetworkExample(options); +} + +export async function runPersistentMultiAgentNetworkExample( + input: { topic: string }, + options: NetworkOptions = {} +) { + const machine = createPersistentMultiAgentNetworkExample(options); + const baseStore = createMemoryRunStore(); + let persistedHandoffSnapshot = false; + + const store = { + append: baseStore.append, + loadEvents: baseStore.loadEvents, + loadLatestSnapshot: baseStore.loadLatestSnapshot, + async saveSnapshot( + snapshot: PersistedSnapshot + ) { + const handoffs = + ((snapshot.snapshot.context as { handoffs?: string[] }).handoffs ?? []); + + if (!persistedHandoffSnapshot && handoffs.length === 1) { + persistedHandoffSnapshot = true; + await baseStore.saveSnapshot(snapshot); + return; + } + + if (!persistedHandoffSnapshot && handoffs.length === 0) { + await baseStore.saveSnapshot(snapshot); + } + }, + }; + + const liveRun = await startSession(machine, { + store, + input, + }); + + await waitForTerminal(() => liveRun.getSnapshot().status); + + const restoredRun = await restoreSession(machine, { + sessionId: liveRun.sessionId, + store, + }); + + await waitForMatch( + () => restoredRun.getSnapshot(), + () => liveRun.getSnapshot() + ); + + return { + sessionId: liveRun.sessionId, + liveSnapshot: liveRun.getSnapshot(), + restoredSnapshot: restoredRun.getSnapshot(), + }; +} + +function expectTerminal(status: string) { + if (status !== 'done' && status !== 'error') { + throw new Error(`Snapshot is not terminal yet: ${status}`); + } +} + +async function waitForTerminal( + getStatus: () => string, + timeoutMs = 1000 +) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + expectTerminal(getStatus()); + return; + } catch {} + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + expectTerminal(getStatus()); +} + +async function waitForMatch( + getActual: () => T, + getExpected: () => T, + timeoutMs = 1000 +) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + if (JSON.stringify(getActual()) === JSON.stringify(getExpected())) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + if (JSON.stringify(getActual()) !== JSON.stringify(getExpected())) { + throw new Error('Snapshots did not converge before timeout.'); + } +} diff --git a/examples/persistent-streaming.ts b/examples/persistent-streaming.ts new file mode 100644 index 0000000..f5f158f --- /dev/null +++ b/examples/persistent-streaming.ts @@ -0,0 +1,138 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + restoreSession, + startSession, +} from '../src/index.js'; + +const textSchema = z.object({ + text: z.string(), +}); + +const textPartSchema = z.object({ + delta: z.string(), +}); + +export function createPersistentStreamingExample( + writeText: (emitPart: (delta: string) => void) => Promise> = (() => { + const chunks = ['hel', 'lo']; + let cursor = 0; + let attempts = 0; + + return async (emitPart) => { + attempts += 1; + + if (attempts === 1) { + emitPart(chunks[cursor++]!); + await new Promise(() => {}); + } + + while (cursor < chunks.length) { + emitPart(chunks[cursor++]!); + } + + return { text: chunks.join('') }; + }; + })() +) { + return createAgentMachine({ + id: 'persistent-streaming-example', + schemas: { + output: textSchema, + emitted: { + textPart: textPartSchema, + }, + }, + context: () => ({ + finalText: '', + }), + initial: 'writing', + states: { + writing: { + schemas: { output: textSchema }, + invoke: async (_args, enq) => + writeText((delta) => { + enq.emit({ type: 'textPart', delta }); + }), + onDone: ({ output }) => ({ + target: 'done', + context: { finalText: output.text }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ text: context.finalText }), + }, + }, + }); +} + +export async function runPersistentStreamingExample( + writeText?: (emitPart: (delta: string) => void) => Promise> +) { + const machine = createPersistentStreamingExample(writeText); + const store = createMemoryRunStore(); + const initialRun = await startSession(machine, { store }); + const initialParts: string[] = []; + + initialRun.on('textPart', (event) => { + initialParts.push(event.delta); + }); + + await waitFor( + () => initialParts.length >= 1 && initialRun.getSnapshot().status === 'active' + ); + + const restoredRun = await restoreSession(machine, { + sessionId: initialRun.sessionId, + store, + }); + const restoredParts: string[] = []; + + restoredRun.on('textPart', (event) => { + restoredParts.push(event.delta); + }); + + await once(restoredRun.onDone.bind(restoredRun)); + + return { + sessionId: initialRun.sessionId, + initialParts, + restoredParts, + initialSnapshot: initialRun.getSnapshot(), + restoredSnapshot: restoredRun.getSnapshot(), + journal: await store.loadEvents(initialRun.sessionId), + }; +} + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +async function waitFor( + predicate: () => boolean, + timeoutMs = 1000 +) { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + if (predicate()) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + if (!predicate()) { + throw new Error('Condition did not become true before timeout.'); + } +} diff --git a/examples/persistent-supervisor.ts b/examples/persistent-supervisor.ts new file mode 100644 index 0000000..9bbd343 --- /dev/null +++ b/examples/persistent-supervisor.ts @@ -0,0 +1,117 @@ +import { + createMemoryRunStore, + restoreSession, + startSession, + type PersistedSnapshot, +} from '../src/index.js'; +import { createSupervisorExample } from './supervisor.js'; + +type SupervisorOptions = Parameters[0]; + +export function createPersistentSupervisorExample( + options: SupervisorOptions = {} +) { + return createSupervisorExample(options); +} + +export async function runPersistentSupervisorExample( + input: { request: string }, + options: SupervisorOptions = {} +) { + const machine = createPersistentSupervisorExample(options); + const baseStore = createMemoryRunStore(); + let persistedRetryHandoff = false; + + const store = { + append: baseStore.append, + loadEvents: baseStore.loadEvents, + loadLatestSnapshot: baseStore.loadLatestSnapshot, + async saveSnapshot(snapshot: PersistedSnapshot) { + const context = snapshot.snapshot.context as { + attemptCount?: number; + history?: string[]; + }; + const history = context.history ?? []; + + if ( + !persistedRetryHandoff + && snapshot.snapshot.value === 'handling' + && context.attemptCount === 1 + && history.some((entry) => entry.startsWith('supervisor:retry:')) + ) { + persistedRetryHandoff = true; + await baseStore.saveSnapshot(snapshot); + return; + } + + if (!persistedRetryHandoff) { + await baseStore.saveSnapshot(snapshot); + } + }, + }; + + const liveRun = await startSession(machine, { + store, + input, + }); + + await waitForTerminal(() => liveRun.getSnapshot().status); + + const restoredRun = await restoreSession(machine, { + sessionId: liveRun.sessionId, + store, + }); + + await waitForMatch( + () => restoredRun.getSnapshot(), + () => liveRun.getSnapshot() + ); + + return { + sessionId: liveRun.sessionId, + liveSnapshot: liveRun.getSnapshot(), + restoredSnapshot: restoredRun.getSnapshot(), + }; +} + +function expectTerminal(status: string) { + if (status !== 'done' && status !== 'error') { + throw new Error(`Snapshot is not terminal yet: ${status}`); + } +} + +async function waitForTerminal( + getStatus: () => string, + timeoutMs = 1000 +) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + try { + expectTerminal(getStatus()); + return; + } catch {} + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + expectTerminal(getStatus()); +} + +async function waitForMatch( + getActual: () => T, + getExpected: () => T, + timeoutMs = 1000 +) { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + if (JSON.stringify(getActual()) === JSON.stringify(getExpected())) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + if (JSON.stringify(getActual()) !== JSON.stringify(getExpected())) { + throw new Error('Snapshots did not converge before timeout.'); + } +} diff --git a/examples/plan-and-execute.ts b/examples/plan-and-execute.ts new file mode 100644 index 0000000..49e97bc --- /dev/null +++ b/examples/plan-and-execute.ts @@ -0,0 +1,175 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const planSchema = z.object({ + plan: z.array(z.string()).min(1).max(5), +}); + +const stepResultSchema = z.object({ + result: z.string(), +}); + +const finalAnswerSchema = z.object({ + answer: z.string(), +}); + +export function createPlanAndExecuteExample( + options: { + plan?: (goal: string) => Promise>; + executeStep?: (args: { + goal: string; + step: string; + priorResults: string[]; + }) => Promise>; + synthesize?: (args: { + goal: string; + plan: string[]; + stepResults: string[]; + }) => Promise>; + } = {} +) { + const planner = + options.plan ?? + ((goal: string) => + generateExampleObject({ + schema: planSchema, + system: 'You are a planner. Break goals into a short actionable sequence.', + prompt: `Create a short plan with 2 to 5 steps for this goal:\n\n${goal}`, + })); + const executeStep = + options.executeStep ?? + ((args: { goal: string; step: string; priorResults: string[] }) => + generateExampleObject({ + schema: stepResultSchema, + system: 'You execute one plan step at a time and report the result concisely.', + prompt: [ + `Goal: ${args.goal}`, + `Current step: ${args.step}`, + args.priorResults.length + ? `Prior results:\n${args.priorResults.map((result, index) => `${index + 1}. ${result}`).join('\n')}` + : 'Prior results: none', + '', + 'Execute the current step conceptually and return a concise result.', + ].join('\n'), + })); + const synthesize = + options.synthesize ?? + ((args: { goal: string; plan: string[]; stepResults: string[] }) => + generateExampleObject({ + schema: finalAnswerSchema, + system: 'You synthesize completed plan results into a final answer.', + prompt: [ + `Goal: ${args.goal}`, + '', + 'Plan:', + ...args.plan.map((step, index) => `${index + 1}. ${step}`), + '', + 'Step results:', + ...args.stepResults.map((result, index) => `${index + 1}. ${result}`), + '', + 'Write the final answer.', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'plan-and-execute-example', + schemas: { + input: z.object({ goal: z.string() }), + output: z.object({ + goal: z.string(), + plan: z.array(z.string()), + stepResults: z.array(z.string()), + answer: z.string().nullable(), + }), + }, + context: (input) => ({ + goal: input.goal, + plan: [] as string[], + stepResults: [] as string[], + answer: null as string | null, + }), + initial: 'planning', + states: { + planning: { + schemas: { output: planSchema }, + invoke: async ({ context }) => planner(context.goal), + onDone: ({ output }) => ({ + target: 'executing', + context: { plan: output.plan }, + input: { index: 0 } + }), + }, + executing: { + schemas: { input: z.object({ + index: z.number().int().min(0), + }), output: stepResultSchema }, + invoke: async ({ context, input }) => + executeStep({ + goal: context.goal, + step: context.plan[input.index] ?? '', + priorResults: context.stepResults, + }), + onDone: ({ output, context }) => { + const nextStepResults = [...context.stepResults, output.result]; + const nextIndex = nextStepResults.length; + + if (nextIndex < context.plan.length) { + return { + target: 'executing' as const, + context: { stepResults: nextStepResults }, + input: { index: nextIndex }, + }; + } + + return { + target: 'synthesizing' as const, + context: { stepResults: nextStepResults }, + }; + }, + }, + synthesizing: { + schemas: { output: finalAnswerSchema }, + invoke: async ({ context }) => + synthesize({ + goal: context.goal, + plan: context.plan, + stepResults: context.stepResults, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { answer: output.answer }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + goal: context.goal, + plan: context.plan, + stepResults: context.stepResults, + answer: context.answer, + }), + }, + }, + }); +} + +async function main() { + try { + const goal = await prompt('Goal'); + const machine = createPlanAndExecuteExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ goal })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/raffle.ts b/examples/raffle.ts index 323672f..b052784 100644 --- a/examples/raffle.ts +++ b/examples/raffle.ts @@ -1,104 +1,132 @@ import { z } from 'zod'; -import { createAgent, fromDecision } from '../src'; -import { openai } from '@ai-sdk/openai'; -import { assign, createActor, log, setup } from 'xstate'; -import { fromTerminal } from './helpers/helpers'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../src/index.js'; +import { + closePrompt, + generateExampleObject, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; -const agent = createAgent({ - name: 'raffle-chooser', - model: openai('gpt-4-turbo'), - events: { - 'agent.collectEntries': z.object({}).describe('Collect more entries'), - 'agent.draw': z.object({}).describe('Draw a winner'), - 'agent.reportWinner': z.object({ - winningEntry: z.string().describe('The winning entry'), - firstRunnerUp: z.string().describe('The first runner up entry'), - secondRunnerUp: z.string().describe('The second runner up entry'), - explanation: z - .string() - .describe('Explanation for why you chose the winning entry'), - }), - }, +const winnerSchema = z.object({ + winningEntry: z.string(), + firstRunnerUp: z.string(), + secondRunnerUp: z.string(), + explanation: z.string(), }); -const machine = setup({ - types: { - context: {} as { - lastInput: string | null; - entries: string[]; - }, - events: {} as typeof agent.types.events | { type: 'draw' }, - }, - actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, -}).createMachine({ - context: { - lastInput: null, - entries: [], - }, - initial: 'entering', - states: { - entering: { - entry: log(({ context }) => context.entries), - invoke: { - src: 'getFromTerminal', - input: 'What technology are you most interested in right now?', - onDone: [ - { - actions: assign({ - lastInput: ({ event }) => event.output, - }), - target: 'determining', - }, - ], +export function createRaffleExample( + pickWinner: (entries: string[]) => Promise> = async ( + entries + ) => + generateExampleObject({ + schema: winnerSchema, + system: 'You are conducting a transparent demo raffle draw.', + prompt: [ + 'Choose one winner and two runners-up from the entries below.', + 'Do not invent names. Explain your selection briefly.', + ...entries.map((entry, index) => `${index + 1}. ${entry}`), + ].join('\n'), + }) +) { + return createAgentMachine({ + id: 'raffle-example', + schemas: { + output: z.object({ + entries: z.array(z.string()), + winner: z.string().nullable(), + firstRunnerUp: z.string().nullable(), + secondRunnerUp: z.string().nullable(), + explanation: z.string().nullable(), + }), + events: { + 'user.entry': z.object({ entry: z.string() }), + 'user.draw': z.object({}), }, }, - determining: { - invoke: { - src: 'agent', - input: { - context: true, - goal: 'If the last input explicitly says to end the drawing and/or choose a winner, start the drawing process. Otherwise, get more entries.', - }, - }, - on: { - 'agent.collectEntries': { - target: 'entering', - actions: assign({ - entries: ({ context }) => [...context.entries, context.lastInput!], - lastInput: null, + context: () => ({ + entries: [] as string[], + winner: null as string | null, + firstRunnerUp: null as string | null, + secondRunnerUp: null as string | null, + explanation: null as string | null, + }), + initial: 'collecting', + states: { + collecting: { + on: { + 'user.entry': ({ event, context }) => ({ + context: { entries: [...context.entries, event.entry] }, + }), + 'user.draw': ({ context }) => ({ + target: context.entries.length >= 3 ? 'drawing' : 'collecting', }), }, - 'agent.draw': 'drawing', }, - }, - drawing: { - entry: log('And the winner is...'), - invoke: { - src: 'agent', - input: { - context: true, - goal: 'Choose the technology that sounds most exciting to you from the entries. Be as unbiased as possible in your choice. Explain why you chose the winning entry.', - }, + drawing: { + schemas: { output: winnerSchema }, + invoke: async ({ context }) => pickWinner(context.entries), + onDone: ({ output }) => ({ + target: 'done', + context: { + winner: output.winningEntry, + firstRunnerUp: output.firstRunnerUp, + secondRunnerUp: output.secondRunnerUp, + explanation: output.explanation, + }, + }), }, - on: { - 'agent.reportWinner': { - actions: log( - ({ event }) => - `\n🎉🎉🎉 ${event.winningEntry} 🎉🎉🎉\n\n${event.explanation}` - ), - target: 'winner', - }, + done: { + type: 'final', + output: ({ context }) => ({ + entries: context.entries, + winner: context.winner, + firstRunnerUp: context.firstRunnerUp, + secondRunnerUp: context.secondRunnerUp, + explanation: context.explanation, + }), }, }, - winner: { - type: 'final', - }, - }, - exit: () => { - process.exit(0); - }, -}); + }); +} + +async function main() { + try { + const machine = createRaffleExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log({ + status: snapshot.status, + value: snapshot.value, + context: snapshot.context, + output: snapshot.output, + }); + break; + } -const actor = createActor(machine); + const entry = await prompt('Entry (blank to draw)'); + await run.send( + entry ? { type: 'user.entry', entry } : { type: 'user.draw' } + ); + } + } finally { + closePrompt(); + } +} -actor.start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/rag.ts b/examples/rag.ts new file mode 100644 index 0000000..08e4376 --- /dev/null +++ b/examples/rag.ts @@ -0,0 +1,118 @@ +import { z } from 'zod'; +import { createAgentMachine, type AgentAdapter } from '../src/index.js'; +import { + closePrompt, + createOpenAiGenerationAdapter, + isMain, + prompt, +} from './_run.js'; + +const retrievedDocumentSchema = z.object({ + id: z.string(), + content: z.string(), +}); + +const retrievedDocumentsSchema = z.object({ + documents: z.array(retrievedDocumentSchema), +}); + +const answerSchema = z.object({ + answer: z.string(), +}); + +export function createRagExample( + options: { + adapter?: AgentAdapter; + retrieve?: (question: string) => Promise>; + } = {} +) { + const retrieve = + options.retrieve ?? + ((question: string) => + Promise.resolve({ + documents: [ + { + id: 'doc-1', + content: `Context about: ${question}`, + }, + { + id: 'doc-2', + content: `Additional supporting detail for: ${question}`, + }, + ], + })); + + return createAgentMachine({ + id: 'rag-example', + adapter: options.adapter ?? createOpenAiGenerationAdapter(), + schemas: { + input: z.object({ + question: z.string(), + }), + output: z.object({ + question: z.string(), + documents: z.array(retrievedDocumentSchema), + answer: z.string().nullable(), + }), + }, + context: (input) => ({ + question: input.question, + documents: [] as Array>, + answer: null as string | null, + }), + initial: 'retrieving', + states: { + retrieving: { + schemas: { output: retrievedDocumentsSchema }, + invoke: async ({ context }) => retrieve(context.question), + onDone: ({ output }) => ({ + target: 'answering', + context: { documents: output.documents }, + }), + }, + answering: { + schemas: { output: answerSchema }, + system: 'Answer the question using only the retrieved documents.', + prompt: ({ context }) => + [ + `Question: ${context.question}`, + '', + 'Documents:', + ...context.documents.map((document) => `- [${document.id}] ${document.content}`), + ].join('\n'), + onDone: ({ output }) => ({ + target: 'done', + context: { answer: output.answer }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + question: context.question, + documents: context.documents, + answer: context.answer, + }), + }, + }, + }); +} + +async function main() { + try { + const question = await prompt('Question'); + const machine = createRagExample(); + const result = await machine.execute( + machine.getInitialState({ question }) + ); + + if (result.status === 'done') { + console.log(result.output); + } + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/react-agent-from-scratch.ts b/examples/react-agent-from-scratch.ts new file mode 100644 index 0000000..c0b22c0 --- /dev/null +++ b/examples/react-agent-from-scratch.ts @@ -0,0 +1,224 @@ +import { z } from 'zod'; +import { createAgentMachine, type StandardSchemaV1 } from '../src/index.js'; + +const messageSchema = z.object({ + role: z.enum(['system', 'user', 'assistant', 'tool']), + content: z.string(), + name: z.string().optional(), +}); + +const toolCallSchema = z.object({ + kind: z.literal('tool'), + toolName: z.string(), + input: z.record(z.string(), z.unknown()), + message: z.string().optional(), +}); + +const finalAnswerSchema = z.object({ + kind: z.literal('final'), + message: z.string(), +}); + +const modelResultSchema = z.discriminatedUnion('kind', [ + toolCallSchema, + finalAnswerSchema, +]); + +const reactOutputSchema = z.object({ + messages: z.array(messageSchema), + finalMessage: z.string().nullable(), + steps: z.number().int().min(0), +}); + +export type ReactAgentMessage = z.infer; + +export type ReactTool = { + name: string; + description: string; + schema?: StandardSchemaV1; + execute: (input: Record) => Promise; +}; + +export type ReactAgentModelResult = z.infer; + +export function createReactAgentFromScratch(options: { + prompt?: string; + maxSteps?: number; + tools?: ReactTool[]; + model: (args: { + messages: ReactAgentMessage[]; + tools: Array<{ + name: string; + description: string; + schema?: StandardSchemaV1; + }>; + }) => Promise; +}) { + const tools = options.tools ?? []; + const maxSteps = options.maxSteps ?? 8; + const toolDefinitions = tools.map(({ name, description, schema }) => ({ + name, + description, + schema, + })); + const toolsByName = new Map(tools.map((tool) => [tool.name, tool])); + + function serializeToolOutput(output: unknown): string { + return typeof output === 'string' ? output : JSON.stringify(output); + } + + return createAgentMachine({ + id: 'react-agent-from-scratch', + schemas: { + input: z.object({ + messages: z.array(messageSchema).optional(), + }), + output: reactOutputSchema, + emitted: { + textPart: z.object({ delta: z.string() }), + toolCall: z.object({ + toolName: z.string(), + input: z.record(z.string(), z.unknown()), + }), + toolResult: z.object({ + toolName: z.string(), + output: z.unknown(), + }), + }, + }, + context: (input) => ({ + messages: [ + ...(options.prompt + ? ([{ role: 'system', content: options.prompt }] satisfies ReactAgentMessage[]) + : []), + ...(input.messages ?? []), + ], + stepCount: 0, + pendingToolCall: + null as { toolName: string; input: Record } | null, + }), + initial: 'agent', + states: { + agent: { + schemas: { output: modelResultSchema }, + invoke: async ({ context }, enq) => { + if (context.stepCount >= maxSteps) { + return { + kind: 'final' as const, + message: 'Stopped because the maximum step count was reached.', + }; + } + + const result = await options.model({ + messages: context.messages, + tools: toolDefinitions, + }); + + if (result.kind === 'final') { + enq.emit({ type: 'textPart', delta: result.message }); + } + + return result; + }, + onDone: ({ output, context }) => { + if (output.kind === 'final') { + return { + target: 'done' as const, + context: { + stepCount: context.stepCount + 1, + messages: [ + ...context.messages, + { + role: 'assistant', + content: output.message, + } satisfies ReactAgentMessage, + ], + }, + }; + } + + return { + target: 'tool' as const, + context: { + stepCount: context.stepCount + 1, + pendingToolCall: { + toolName: output.toolName, + input: output.input, + }, + messages: [ + ...context.messages, + { + role: 'assistant', + content: + output.message + ?? `Calling tool ${output.toolName} with ${JSON.stringify(output.input)}`, + } satisfies ReactAgentMessage, + ], + }, + input: { + toolName: output.toolName, + input: output.input, + }, + }; + }, + }, + tool: { + schemas: { input: z.object({ + toolName: z.string(), + input: z.record(z.string(), z.unknown()), + }), output: z.object({ + toolName: z.string(), + output: z.unknown(), + }) }, + invoke: async ({ input }, enq) => { + const tool = toolsByName.get(input.toolName); + + if (!tool) { + throw new Error(`Tool '${input.toolName}' not found`); + } + + enq.emit({ + type: 'toolCall', + toolName: input.toolName, + input: input.input, + }); + + const output = await tool.execute(input.input); + + enq.emit({ + type: 'toolResult', + toolName: input.toolName, + output, + }); + + return { + toolName: input.toolName, + output, + }; + }, + onDone: ({ output, context }) => ({ + target: 'agent' as const, + context: { + pendingToolCall: null, + messages: [ + ...context.messages, + { + role: 'tool', + name: output.toolName, + content: serializeToolOutput(output.output), + } satisfies ReactAgentMessage, + ], + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + messages: context.messages, + finalMessage: context.messages.at(-1)?.content ?? null, + steps: context.stepCount, + }), + }, + }, + }); +} diff --git a/examples/react-agent.ts b/examples/react-agent.ts new file mode 100644 index 0000000..8e20a84 --- /dev/null +++ b/examples/react-agent.ts @@ -0,0 +1,100 @@ +import { z } from 'zod'; +import { + createMemoryRunStore, + startSession, +} from '../src/index.js'; +import { createReactAgentFromScratch } from './react-agent-from-scratch.js'; +import { + closePrompt, + generateExampleObject, + generateExampleText, + isMain, + prompt, + waitForRunDone, +} from './_run.js'; + +const reactModelResultSchema = z.discriminatedUnion('kind', [ + z.object({ + kind: z.literal('tool'), + toolName: z.literal('search'), + input: z.object({ + query: z.string(), + }), + message: z.string().optional(), + }), + z.object({ + kind: z.literal('final'), + message: z.string(), + }), +]); + +export function createReactAgentExample(options: { + search?: (query: string) => Promise; + model?: (args: { + messages: Array<{ + role: 'system' | 'user' | 'assistant' | 'tool'; + content: string; + name?: string; + }>; + }) => Promise>; +} = {}) { + return createReactAgentFromScratch({ + prompt: 'You are a helpful assistant.', + tools: [ + { + name: 'search', + description: 'Searches the knowledge base.', + execute: async (input) => + (options.search + ?? ((query) => + generateExampleText({ + system: 'You are a concise search backend returning a short factual result snippet.', + prompt: `Return a short search result snippet for the query: ${query}`, + })))(String(input.query)), + }, + ], + model: + options.model + ?? (({ messages }) => + generateExampleObject({ + schema: reactModelResultSchema, + system: [ + 'You are a ReAct-style assistant.', + 'If you still need outside information, call the search tool.', + 'If the latest tool result is enough, answer directly with kind="final".', + ].join('\n'), + prompt: messages + .map((message) => `${message.role.toUpperCase()}: ${message.content}`) + .join('\n'), + })), + }); +} + +async function main() { + try { + const message = await prompt('User'); + const agent = createReactAgentExample(); + const run = await startSession(agent, { + store: createMemoryRunStore(), + input: { + messages: [{ role: 'user', content: message }], + }, + }); + + run.on('toolCall', (event) => { + console.log(`Calling ${event.toolName}(${event.input.query})`); + }); + run.on('toolResult', (event) => { + console.log(`${event.toolName} -> ${String(event.output)}`); + }); + + const done = await waitForRunDone(run); + console.log(done.output); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/reflection.ts b/examples/reflection.ts new file mode 100644 index 0000000..f7f5ee7 --- /dev/null +++ b/examples/reflection.ts @@ -0,0 +1,161 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const draftSchema = z.object({ + draft: z.string(), +}); + +const feedbackSchema = z.object({ + feedback: z.string().nullable(), +}); + +export function createReflectionExample( + options: { + draft?: (task: string) => Promise>; + reflect?: (args: { + task: string; + draft: string; + revisionCount: number; + }) => Promise>; + revise?: (args: { + task: string; + draft: string; + feedback: string; + }) => Promise>; + maxRevisions?: number; + } = {} +) { + const draft = + options.draft ?? + ((task: string) => + generateExampleObject({ + schema: draftSchema, + system: 'You write concise first drafts.', + prompt: `Write a short draft for this task:\n\n${task}`, + })); + const reflect = + options.reflect ?? + ((args: { task: string; draft: string; revisionCount: number }) => + generateExampleObject({ + schema: feedbackSchema, + system: 'You critique drafts and return null when no more revision is needed.', + prompt: [ + `Task: ${args.task}`, + `Revision count: ${args.revisionCount}`, + '', + 'Draft:', + args.draft, + '', + 'Return one concise revision note, or null if the draft is already good enough.', + ].join('\n'), + })); + const revise = + options.revise ?? + ((args: { task: string; draft: string; feedback: string }) => + generateExampleObject({ + schema: draftSchema, + system: 'You revise drafts to address the provided feedback.', + prompt: [ + `Task: ${args.task}`, + `Feedback: ${args.feedback}`, + '', + 'Current draft:', + args.draft, + '', + 'Revise the draft.', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'reflection-example', + schemas: { + input: z.object({ task: z.string() }), + output: z.object({ + task: z.string(), + draft: z.string().nullable(), + feedback: z.string().nullable(), + revisionCount: z.number(), + }), + }, + context: (input) => ({ + task: input.task, + draft: null as string | null, + feedback: null as string | null, + revisionCount: 0, + maxRevisions: options.maxRevisions ?? 2, + }), + initial: 'drafting', + states: { + drafting: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => draft(context.task), + onDone: ({ output }) => ({ + target: 'reflecting', + context: { draft: output.draft }, + }), + }, + reflecting: { + schemas: { output: feedbackSchema }, + invoke: async ({ context }) => + reflect({ + task: context.task, + draft: context.draft ?? '', + revisionCount: context.revisionCount, + }), + onDone: ({ output, context }) => ({ + target: + !output.feedback || context.revisionCount >= context.maxRevisions + ? 'done' + : 'revising', + context: { feedback: output.feedback }, + }), + }, + revising: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => + revise({ + task: context.task, + draft: context.draft ?? '', + feedback: context.feedback ?? '', + }), + onDone: ({ output, context }) => ({ + target: 'reflecting', + context: { + draft: output.draft, + revisionCount: context.revisionCount + 1, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + task: context.task, + draft: context.draft, + feedback: context.feedback, + revisionCount: context.revisionCount, + }), + }, + }, + }); +} + +async function main() { + try { + const task = await prompt('Task'); + const machine = createReflectionExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ task })))); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/rewoo.ts b/examples/rewoo.ts new file mode 100644 index 0000000..45f8547 --- /dev/null +++ b/examples/rewoo.ts @@ -0,0 +1,234 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const rewooPlanSchema = z.object({ + steps: z + .array( + z.object({ + id: z.string().regex(/^E\d+$/), + instruction: z.string(), + input: z.string(), + }) + ) + .min(1) + .max(5), +}); + +const rewooStepResultSchema = z.object({ + result: z.string(), +}); + +const rewooAnswerSchema = z.object({ + answer: z.string(), +}); + +type RewooPlan = z.infer; +type RewooStep = RewooPlan['steps'][number]; + +function resolveStepInput( + template: string, + resultsById: Record +): string { + return template.replace(/#(E\d+)/g, (_match, id: string) => resultsById[id] ?? ''); +} + +export function createRewooExample( + options: { + plan?: (objective: string) => Promise; + executeStep?: (args: { + objective: string; + step: RewooStep; + resolvedInput: string; + resultsById: Record; + }) => Promise>; + solve?: (args: { + objective: string; + steps: RewooPlan['steps']; + resultsById: Record; + }) => Promise>; + } = {} +) { + const plan = + options.plan ?? + ((objective: string) => + generateExampleObject({ + schema: rewooPlanSchema, + system: [ + 'You are a ReWOO-style planner.', + 'Produce a short sequence of executable steps.', + 'Each step must have an id like E1, E2, E3.', + 'Later step inputs may reference earlier outputs using #E1, #E2, etc.', + ].join('\n'), + prompt: `Create a compact executable plan for this objective:\n\n${objective}`, + })); + + const executeStep = + options.executeStep ?? + ((args: { + objective: string; + step: RewooStep; + resolvedInput: string; + resultsById: Record; + }) => + generateExampleObject({ + schema: rewooStepResultSchema, + system: 'You execute one specialist step at a time and return a concise result.', + prompt: [ + `Objective: ${args.objective}`, + `Step id: ${args.step.id}`, + `Instruction: ${args.step.instruction}`, + `Resolved input: ${args.resolvedInput}`, + Object.keys(args.resultsById).length + ? `Prior results:\n${Object.entries(args.resultsById) + .map(([id, value]) => `${id}: ${value}`) + .join('\n')}` + : 'Prior results: none', + ].join('\n'), + })); + + const solve = + options.solve ?? + ((args: { + objective: string; + steps: RewooPlan['steps']; + resultsById: Record; + }) => + generateExampleObject({ + schema: rewooAnswerSchema, + system: 'You synthesize completed step results into a direct final answer.', + prompt: [ + `Objective: ${args.objective}`, + '', + 'Completed steps:', + ...args.steps.map((step) => `${step.id}. ${step.instruction}`), + '', + 'Results:', + ...Object.entries(args.resultsById).map(([id, value]) => `${id}: ${value}`), + '', + 'Write the final answer.', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'rewoo-example', + schemas: { + input: z.object({ objective: z.string() }), + output: z.object({ + objective: z.string(), + steps: rewooPlanSchema.shape.steps, + resultsById: z.record(z.string(), z.string()), + answer: z.string().nullable(), + }), + }, + context: (input) => ({ + objective: input.objective, + steps: [] as RewooPlan['steps'], + resultsById: {} as Record, + answer: null as string | null, + }), + initial: 'planning', + states: { + planning: { + schemas: { output: rewooPlanSchema }, + invoke: async ({ context }) => plan(context.objective), + onDone: ({ output }) => ({ + target: 'executing', + context: { steps: output.steps }, + input: { index: 0 }, + }), + }, + executing: { + schemas: { input: z.object({ + index: z.number().int().min(0), + }), output: z.object({ + stepId: z.string(), + result: z.string(), + }) }, + invoke: async ({ context, input }) => { + const step = context.steps[input.index]; + + if (!step) { + throw new Error(`Missing step at index ${input.index}`); + } + + const resolvedInput = resolveStepInput(step.input, context.resultsById); + const outcome = await executeStep({ + objective: context.objective, + step, + resolvedInput, + resultsById: context.resultsById, + }); + + return { + stepId: step.id, + result: outcome.result, + }; + }, + onDone: ({ output, context }) => { + const nextResultsById = { + ...context.resultsById, + [output.stepId]: output.result, + }; + const nextIndex = Object.keys(nextResultsById).length; + + if (nextIndex < context.steps.length) { + return { + target: 'executing', + context: { resultsById: nextResultsById }, + input: { index: nextIndex }, + }; + } + + return { + target: 'solving', + context: { resultsById: nextResultsById }, + }; + }, + }, + solving: { + schemas: { output: rewooAnswerSchema }, + invoke: async ({ context }) => + solve({ + objective: context.objective, + steps: context.steps, + resultsById: context.resultsById, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { answer: output.answer }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + objective: context.objective, + steps: context.steps, + resultsById: context.resultsById, + answer: context.answer, + }), + }, + }, + }); +} + +async function main() { + try { + const objective = await prompt('Objective'); + const machine = createRewooExample(); + const result = await machine.execute(machine.getInitialState({ objective })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/river-crossing.ts b/examples/river-crossing.ts new file mode 100644 index 0000000..497c2e5 --- /dev/null +++ b/examples/river-crossing.ts @@ -0,0 +1,179 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { formatResult, isMain } from './_run.js'; + +const bankItem = z.enum(['wolf', 'goat', 'cabbage']); + +const crossingMoveSchema = z.object({ + move: z.enum(['takeGoat', 'takeWolf', 'takeCabbage', 'returnEmpty', 'done']), + reasoning: z.string(), +}); + +const crossingStateSchema = z.object({ + leftBank: z.array(bankItem), + rightBank: z.array(bankItem), + farmerPosition: z.enum(['left', 'right']), + step: z.string(), +}); + +function chooseCrossingMove( + leftBank: string[], + rightBank: string[], + farmerPosition: 'left' | 'right' +): z.infer { + const key = `${farmerPosition}|${leftBank.sort().join(',')}|${rightBank.sort().join(',')}`; + const plan: Record> = { + 'left|cabbage,goat,wolf|': { + move: 'takeGoat', + reasoning: 'Move the goat first so it is not left with the cabbage.', + }, + 'right|cabbage,wolf|goat': { + move: 'returnEmpty', + reasoning: 'Return alone to ferry another item.', + }, + 'left|cabbage,wolf|goat': { + move: 'takeWolf', + reasoning: 'Take the wolf across while the goat waits safely alone.', + }, + 'right|cabbage|goat,wolf': { + move: 'takeGoat', + reasoning: 'Bring the goat back so the wolf is not left with it.', + }, + 'left|cabbage,goat|wolf': { + move: 'takeCabbage', + reasoning: 'Take the cabbage across now that the goat is with you.', + }, + 'right|goat|cabbage,wolf': { + move: 'returnEmpty', + reasoning: 'Return alone to fetch the goat.', + }, + 'left|goat|cabbage,wolf': { + move: 'takeGoat', + reasoning: 'Bring the goat across to complete the crossing.', + }, + 'right||cabbage,goat,wolf': { + move: 'done', + reasoning: 'Everyone is safely across.', + }, + }; + + return plan[key] ?? { move: 'done', reasoning: 'No further move required.' }; +} + +function moveItem( + leftBank: Array<'wolf' | 'goat' | 'cabbage'>, + rightBank: Array<'wolf' | 'goat' | 'cabbage'>, + farmerPosition: 'left' | 'right', + move: z.infer['move'] +): z.infer { + const fromLeft = farmerPosition === 'left'; + + if (move === 'returnEmpty') { + return { + leftBank, + rightBank, + farmerPosition: fromLeft ? 'right' : 'left', + step: 'The farmer crossed the river alone.', + }; + } + + const item = move.replace(/^take/, '').toLowerCase() as 'wolf' | 'goat' | 'cabbage'; + return { + leftBank: fromLeft + ? leftBank.filter((value) => value !== item) + : [...leftBank, item].sort() as Array<'wolf' | 'goat' | 'cabbage'>, + rightBank: fromLeft + ? [...rightBank, item].sort() as Array<'wolf' | 'goat' | 'cabbage'> + : rightBank.filter((value) => value !== item), + farmerPosition: fromLeft ? 'right' : 'left', + step: `The farmer took the ${item} across the river.`, + }; +} + +export function createRiverCrossingExample() { + return createAgentMachine({ + id: 'river-crossing-example', + schemas: { + output: z.object({ + leftBank: z.array(bankItem), + rightBank: z.array(bankItem), + steps: z.array(z.string()), + reasoning: z.array(z.string()), + }), + }, + context: () => ({ + leftBank: ['wolf', 'goat', 'cabbage'] as Array<'wolf' | 'goat' | 'cabbage'>, + rightBank: [] as Array<'wolf' | 'goat' | 'cabbage'>, + farmerPosition: 'left' as 'left' | 'right', + steps: [] as string[], + reasoning: [] as string[], + }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: crossingMoveSchema }, + invoke: async ({ context }) => + chooseCrossingMove( + [...context.leftBank], + [...context.rightBank], + context.farmerPosition + ), + onDone: ({ output, context }) => { + const nextReasoning = [...context.reasoning, output.reasoning]; + + if (output.move === 'done') { + return { + target: 'done' as const, + context: { reasoning: nextReasoning }, + }; + } + + return { + target: 'moving' as const, + input: { move: output.move }, + context: { reasoning: nextReasoning }, + }; + }, + }, + moving: { + schemas: { input: z.object({ + move: crossingMoveSchema.shape.move.exclude(['done']), + }), output: crossingStateSchema }, + invoke: async ({ context, input }) => + moveItem( + [...context.leftBank], + [...context.rightBank], + context.farmerPosition, + input.move as 'takeGoat' | 'takeWolf' | 'takeCabbage' | 'returnEmpty' + ), + onDone: ({ output, context }) => ({ + target: 'choosing', + context: { + leftBank: output.leftBank, + rightBank: output.rightBank, + farmerPosition: output.farmerPosition, + steps: [...context.steps, output.step], + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + leftBank: context.leftBank, + rightBank: context.rightBank, + steps: context.steps, + reasoning: context.reasoning, + }), + }, + }, + }); +} + +async function main() { + const machine = createRiverCrossingExample(); + console.log(formatResult(await machine.execute(machine.getInitialState()))); +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/self-evaluation-loop-flow.ts b/examples/self-evaluation-loop-flow.ts new file mode 100644 index 0000000..130e6eb --- /dev/null +++ b/examples/self-evaluation-loop-flow.ts @@ -0,0 +1,133 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const postSchema = z.object({ + post: z.string(), +}); + +const evaluationSchema = z.object({ + valid: z.boolean(), + feedback: z.string().nullable(), +}); + +export function createSelfEvaluationLoopFlowExample(options: { + generatePost?: (args: { + topic: string; + feedback: string | null; + attempt: number; + }) => Promise>; + evaluatePost?: (post: string) => Promise>; + maxAttempts?: number; +} = {}) { + const generatePost = + options.generatePost ?? + ((args: { topic: string; feedback: string | null; attempt: number }) => + generateExampleObject({ + schema: postSchema, + system: 'Write a playful X post in a Shakespearean tone with no emojis and under 280 characters.', + prompt: [ + `Topic: ${args.topic}`, + `Attempt: ${args.attempt}`, + args.feedback ? `Feedback to address: ${args.feedback}` : 'Feedback: none', + ].join('\n'), + })); + + const evaluatePost = + options.evaluatePost ?? + ((post: string) => + generateExampleObject({ + schema: evaluationSchema, + system: + 'Validate whether the X post is under 280 characters, uses no emojis, and stays playful. Return feedback only when it should be revised.', + prompt: post, + })); + + return createAgentMachine({ + id: 'self-evaluation-loop-flow-example', + schemas: { + input: z.object({ + topic: z.string(), + }), + output: z.object({ + post: z.string().nullable(), + valid: z.boolean(), + feedback: z.string().nullable(), + attempt: z.number(), + }), + }, + context: (input) => ({ + topic: input.topic, + post: null as string | null, + valid: false, + feedback: null as string | null, + attempt: 1, + maxAttempts: options.maxAttempts ?? 3, + }), + initial: 'generating', + states: { + generating: { + schemas: { output: postSchema }, + invoke: async ({ context }) => + generatePost({ + topic: context.topic, + feedback: context.feedback, + attempt: context.attempt, + }), + onDone: ({ output }) => ({ + target: 'evaluating', + context: { + post: output.post, + }, + }), + }, + evaluating: { + schemas: { output: evaluationSchema }, + invoke: async ({ context }) => evaluatePost(context.post ?? ''), + onDone: ({ output, context }) => ({ + target: + output.valid || context.attempt >= context.maxAttempts + ? 'done' + : 'generating', + context: { + valid: output.valid, + feedback: output.feedback, + attempt: output.valid + ? context.attempt + : context.attempt + 1, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + post: context.post, + valid: context.valid, + feedback: context.feedback, + attempt: context.attempt, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const machine = createSelfEvaluationLoopFlowExample(); + const result = await machine.execute(machine.getInitialState({ topic })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/simple.ts b/examples/simple.ts index 25c8bab..ef4e86d 100644 --- a/examples/simple.ts +++ b/examples/simple.ts @@ -1,39 +1,62 @@ -import { createAgent, fromDecision } from '../src'; import { z } from 'zod'; -import { setup, createActor } from 'xstate'; -import { openai } from '@ai-sdk/openai'; +import { createAgentMachine, type AgentAdapter } from '../src/index.js'; +import { + closePrompt, + createOpenAiGenerationAdapter, + formatResult, + isMain, + prompt, +} from './_run.js'; -const agent = createAgent({ - name: 'simple', - model: openai('gpt-3.5-turbo-16k-0613'), - events: { - 'agent.thought': z.object({ - text: z.string().describe('The text of the thought'), - }), - }, +const summarySchema = z.object({ + summary: z.string(), }); -const machine = setup({ - actors: { agent: fromDecision(agent) }, -}).createMachine({ - initial: 'thinking', - states: { - thinking: { - invoke: { - src: 'agent', - input: 'Think about a random topic, and then share that thought.', +export function createSimpleExample( + adapter: AgentAdapter = createOpenAiGenerationAdapter() +) { + return createAgentMachine({ + id: 'simple-example', + adapter, + schemas: { + input: z.object({ text: z.string() }), + output: z.object({ summary: z.string().nullable() }), + }, + context: (input) => ({ + text: input.text, + summary: null as string | null, + }), + initial: 'summarizing', + states: { + summarizing: { + schemas: { output: summarySchema }, + prompt: ({ context }) => + `Summarize this text in one sentence:\n\n${context.text}`, + onDone: ({ output }) => ({ + target: 'done', + context: { summary: output.summary }, + }), }, - on: { - 'agent.thought': { - actions: ({ event }) => console.log(event.text), - target: 'thought', - }, + done: { + type: 'final', + output: ({ context }) => ({ summary: context.summary }), }, }, - thought: { - type: 'final', - }, - }, -}); + }); +} + +async function main() { + try { + const text = await prompt('Text to summarize'); + const machine = createSimpleExample(); + const result = await machine.execute(machine.getInitialState({ text })); + + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} -const actor = createActor(machine).start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/spec-agent-loop.ts b/examples/spec-agent-loop.ts new file mode 100644 index 0000000..2968cb4 --- /dev/null +++ b/examples/spec-agent-loop.ts @@ -0,0 +1,274 @@ +import { z } from 'zod'; +import { + appendMessages, + assistantMessage, + createAgentMachine, + createMemoryRunStore, + startSession, + userMessage, +} from '../src/index.js'; +import { + closePrompt, + generateExampleText, + isMain, + prompt, + waitForRunSnapshot, +} from './_run.js'; + +const generationSchema = z.object({ + rawText: z.string(), + specYaml: z.string(), + questions: z.array(z.string()), + status: z.enum(['needs_user', 'complete']), +}); + +const validationSchema = z.object({ + ok: z.boolean(), + errors: z.array(z.string()), +}); + +type Generation = z.infer; + +export function createSpecAgentLoopExample( + options: { + generate?: (args: { + specName: string; + messages: Array<{ role: string; content: string }>; + }) => Promise; + validate?: (yaml: string) => z.infer; + maxRepairTurns?: number; + } = {} +) { + const generate = + options.generate ?? + (({ specName, messages }) => + generateExampleText({ + system: [ + 'Write a small YAML spec.', + 'Respond exactly with , , and tags.', + 'Use complete only when the YAML has no __HOLE__ markers.', + ].join('\n'), + prompt: [ + `Spec name: ${specName}`, + '', + ...messages.map((message) => `${message.role}: ${message.content}`), + ].join('\n'), + })); + + const validate = + options.validate ?? + ((yaml: string) => { + const errors: string[] = []; + if (!yaml.trim()) errors.push('Missing YAML'); + if (/__HOLE__|TODO|TBD|UNKNOWN/i.test(yaml)) errors.push('YAML has holes'); + if (!/^name:/m.test(yaml)) errors.push('Missing name'); + return { ok: errors.length === 0, errors }; + }); + + return createAgentMachine({ + id: 'spec-agent-loop-example', + schemas: { + input: z.object({ + specName: z.string(), + prompt: z.string(), + }), + events: { + 'user.answer': z.object({ answer: z.string() }), + 'user.accept': z.object({}), + 'user.quit': z.object({}), + }, + output: z.object({ + specYaml: z.string(), + accepted: z.boolean(), + }), + }, + context: (input) => ({ + specName: input.specName, + specYaml: '', + questions: [] as string[], + status: 'needs_user' as Generation['status'], + validation: { ok: false, errors: [] as string[] }, + repairTurns: 0, + maxRepairTurns: options.maxRepairTurns ?? 3, + accepted: false, + }), + messages: (input) => [ + userMessage(`Create an initial spec from this prompt:\n\n${input.prompt}`), + ], + initial: 'generating', + states: { + generating: { + schemas: { output: generationSchema }, + invoke: async ({ context, messages }) => + parseTaggedResponse( + await generate({ + specName: context.specName, + messages, + }) + ), + onDone: ({ output, messages }) => ({ + target: output.specYaml ? 'validating' : 'repairing', + context: { + specYaml: output.specYaml, + questions: output.questions, + status: output.status, + }, + messages: appendMessages(messages, assistantMessage(output.rawText)), + }), + }, + validating: { + schemas: { output: validationSchema }, + invoke: async ({ context }) => validate(context.specYaml), + onDone: ({ output }) => ({ + target: 'routing', + context: { validation: output }, + }), + }, + routing: { + always: ({ context, messages }) => { + if (context.validation.ok && context.status === 'complete') { + return { target: 'awaitingAcceptance' }; + } + + if (!context.validation.ok && context.status === 'complete') { + return { + target: + context.repairTurns < context.maxRepairTurns + ? 'generating' + : 'awaitingUser', + context: { repairTurns: context.repairTurns + 1 }, + messages: appendMessages( + messages, + userMessage( + [ + 'You marked the spec complete, but deterministic validation failed.', + ...context.validation.errors.map((error) => `- ${error}`), + ].join('\n') + ) + ), + }; + } + + return { target: 'awaitingUser' }; + }, + }, + repairing: { + always: ({ context, messages }) => ({ + target: + context.repairTurns < context.maxRepairTurns + ? 'generating' + : 'awaitingUser', + context: { repairTurns: context.repairTurns + 1 }, + messages: appendMessages( + messages, + userMessage('Return the full YAML spec using the required tags.') + ), + }), + }, + awaitingUser: { + on: { + 'user.answer': ({ event, messages }) => ({ + target: 'generating', + context: { repairTurns: 0 }, + messages: appendMessages( + messages, + userMessage(`User answered/refined:\n\n${event.answer}`) + ), + }), + 'user.quit': { target: 'done' }, + }, + }, + awaitingAcceptance: { + on: { + 'user.accept': { + target: 'done', + context: { accepted: true }, + }, + 'user.answer': ({ event, messages }) => ({ + target: 'generating', + messages: appendMessages( + messages, + userMessage(`Spec validates, but refine:\n\n${event.answer}`) + ), + }), + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + specYaml: context.specYaml, + accepted: context.accepted, + }), + }, + }, + }); +} + +function parseTaggedResponse(text: string): Generation { + const specYaml = text.match(/([\s\S]*?)<\/SPEC_YAML>/)?.[1]?.trim() ?? ''; + const questionText = text.match(/([\s\S]*?)<\/QUESTIONS>/)?.[1]?.trim() ?? ''; + const statusRaw = text.match(/([\s\S]*?)<\/STATUS>/)?.[1]?.trim(); + + return { + rawText: text.trim(), + specYaml, + questions: questionText + .split('\n') + .map((line) => line.replace(/^[-*\d. )]+/, '').trim()) + .filter(Boolean), + status: statusRaw === 'complete' ? 'complete' : 'needs_user', + }; +} + +async function main() { + try { + const specName = await prompt('Spec name'); + const initialPrompt = await prompt('Describe the spec'); + const machine = createSpecAgentLoopExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { specName, prompt: initialPrompt }, + }); + + while (true) { + const snapshot = await waitForRunSnapshot( + run, + (nextSnapshot) => nextSnapshot.status !== 'active' + ); + + if (snapshot.status === 'done') { + console.log(snapshot.output); + break; + } + + console.log({ + value: snapshot.value, + validation: snapshot.context.validation, + questions: snapshot.context.questions, + }); + + if (snapshot.value === 'awaitingAcceptance') { + const answer = await prompt('Accept? [Y/n]'); + await run.send( + !answer || /^y(es)?$/i.test(answer) + ? { type: 'user.accept' } + : { type: 'user.answer', answer } + ); + continue; + } + + const answer = await prompt('Answer, refine, or /quit'); + await run.send( + answer === '/quit' + ? { type: 'user.quit' } + : { type: 'user.answer', answer } + ); + } + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/sql-agent.ts b/examples/sql-agent.ts new file mode 100644 index 0000000..e5f35f3 --- /dev/null +++ b/examples/sql-agent.ts @@ -0,0 +1,272 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + decide, + decideResultSchema, + startSession, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + generateExampleObject, + isMain, + prompt, + waitForRunDone, +} from './_run.js'; + +const sqlValueSchema = z.union([z.string(), z.number(), z.null()]); +const sqlRowsSchema = z.array(z.record(z.string(), sqlValueSchema)); + +const planningOptions = { + query: { + description: 'Write or revise a SQL query that should help answer the question.', + schema: z.object({ + query: z.string(), + }), + }, + answer: { + description: 'Return the final answer once the available query results are sufficient.', + schema: z.object({ + answer: z.string(), + }), + }, +} as const; + +const queryExecutionSchema = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('success'), + query: z.string(), + rows: sqlRowsSchema, + }), + z.object({ + status: z.literal('error'), + query: z.string(), + error: z.string(), + }), +]); + +export function createSqlAgentExample( + options: { + adapter?: DecideAdapter; + executeQuery?: (args: { + question: string; + schema: string; + query: string; + queryHistory: string[]; + }) => Promise< + | { status: 'success'; rows: z.infer } + | { status: 'error'; error: string } + >; + } = {} +) { + const adapter = options.adapter ?? createOpenAiDecisionAdapter(); + const executeQuery = + options.executeQuery ?? + ((args: { + question: string; + schema: string; + query: string; + queryHistory: string[]; + }) => + generateExampleObject({ + schema: z.discriminatedUnion('status', [ + z.object({ + status: z.literal('success'), + rows: sqlRowsSchema, + }), + z.object({ + status: z.literal('error'), + error: z.string(), + }), + ]), + system: [ + 'You simulate a SQL database tool for demos.', + 'Return status="success" with concise rows when the query is plausible.', + 'Return status="error" with a short SQL/tool error when the query is invalid.', + ].join('\n'), + prompt: [ + `Question: ${args.question}`, + `Schema: ${args.schema}`, + `Query: ${args.query}`, + args.queryHistory.length + ? `Prior queries:\n${args.queryHistory.map((query, index) => `${index + 1}. ${query}`).join('\n')}` + : 'Prior queries: none', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'sql-agent-example', + schemas: { + input: z.object({ + question: z.string(), + schema: z.string(), + }), + emitted: { + toolCall: z.object({ + toolName: z.literal('sqlDb'), + input: z.object({ + query: z.string(), + }), + }), + toolResult: z.object({ + toolName: z.literal('sqlDb'), + output: queryExecutionSchema, + }), + }, + output: z.object({ + question: z.string(), + schema: z.string(), + answer: z.string().nullable(), + latestRows: sqlRowsSchema.nullable(), + latestError: z.string().nullable(), + queryHistory: z.array(z.string()), + }), + }, + context: (input) => ({ + question: input.question, + schema: input.schema, + answer: null as string | null, + latestRows: null as z.infer | null, + latestError: null as string | null, + queryHistory: [] as string[], + }), + initial: 'planning', + states: { + planning: { + schemas: { output: decideResultSchema(planningOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'You are a SQL agent deciding whether to query the database again or answer.', + 'Query when you still need database evidence or when the last query failed.', + 'Answer only when the current rows are enough to respond directly.', + '', + `Question: ${context.question}`, + `Schema: ${context.schema}`, + context.queryHistory.length + ? `Previous queries:\n${context.queryHistory.map((query, index) => `${index + 1}. ${query}`).join('\n')}` + : 'Previous queries: none', + context.latestError + ? `Latest error: ${context.latestError}` + : 'Latest error: none', + context.latestRows + ? `Latest rows:\n${JSON.stringify(context.latestRows, null, 2)}` + : 'Latest rows: none', + ].join('\n'), + options: planningOptions, + }), + onDone: ({ output }) => { + if (output.choice === 'query') { + return { + target: 'querying', + input: { + query: output.data.query, + }, + }; + } + + return { + target: 'done', + context: { + answer: output.data.answer, + }, + }; + }, + }, + querying: { + schemas: { input: z.object({ + query: z.string(), + }), output: queryExecutionSchema }, + invoke: async ({ context, input }, enq) => { + enq.emit({ + type: 'toolCall', + toolName: 'sqlDb', + input, + }); + + const output = await executeQuery({ + question: context.question, + schema: context.schema, + query: input.query, + queryHistory: context.queryHistory, + }); + + const resolvedOutput = + output.status === 'success' + ? { + status: 'success' as const, + query: input.query, + rows: output.rows, + } + : { + status: 'error' as const, + query: input.query, + error: output.error, + }; + + enq.emit({ + type: 'toolResult', + toolName: 'sqlDb', + output: resolvedOutput, + }); + + return resolvedOutput; + }, + onDone: ({ output, context }) => ({ + target: 'planning', + context: { + queryHistory: [ + ...context.queryHistory, + output.query, + ], + latestRows: output.status === 'success' ? output.rows : null, + latestError: output.status === 'error' ? output.error : null, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + question: context.question, + schema: context.schema, + answer: context.answer, + latestRows: context.latestRows, + latestError: context.latestError, + queryHistory: context.queryHistory, + }), + }, + }, + }); +} + +async function main() { + try { + const question = await prompt('Question'); + const schema = await prompt('Schema'); + const machine = createSqlAgentExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { question, schema }, + }); + + run.on('toolCall', (event) => { + console.log(`Calling ${event.toolName}(${event.input.query})`); + }); + run.on('toolResult', (event) => { + console.log(`${event.toolName} -> ${JSON.stringify(event.output)}`); + }); + + const done = await waitForRunDone(run); + console.log(done.output); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/subflow.ts b/examples/subflow.ts new file mode 100644 index 0000000..cb6639c --- /dev/null +++ b/examples/subflow.ts @@ -0,0 +1,144 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const researchSchema = z.object({ + bullets: z.array(z.string()), +}); + +const draftSchema = z.object({ + draft: z.string(), +}); + +export function createSubflowExample( + options: { + research?: (topic: string) => Promise>; + write?: (input: { + topic: string; + bullets: string[]; + }) => Promise>; + } = {} +) { + const childMachine = createAgentMachine({ + id: 'subflow-child', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ bullets: z.array(z.string()) }), + }, + context: (input) => ({ + topic: input.topic, + bullets: [] as string[], + }), + initial: 'researching', + states: { + researching: { + schemas: { output: researchSchema }, + invoke: async ({ context }) => + (options.research + ?? ((topic) => + generateExampleObject({ + schema: researchSchema, + system: 'You research a topic and return concise bullet points.', + prompt: `Return 2 to 4 concise research bullets about ${topic}.`, + })))(context.topic), + onDone: ({ output }) => ({ + target: 'done', + context: { bullets: output.bullets }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ bullets: context.bullets }), + }, + }, + }); + + return createAgentMachine({ + id: 'subflow-example', + schemas: { + input: z.object({ topic: z.string() }), + output: z.object({ + bullets: z.array(z.string()), + draft: z.string().nullable(), + }), + }, + context: (input) => ({ + topic: input.topic, + bullets: [] as string[], + draft: null as string | null, + }), + initial: 'researching', + states: { + researching: { + schemas: { output: researchSchema }, + invoke: async ({ context }) => { + const result = await childMachine.execute( + childMachine.getInitialState({ topic: context.topic }) + ); + + if (result.status !== 'done') { + throw new Error('Child machine did not finish'); + } + + return { + bullets: result.output.bullets, + }; + }, + onDone: ({ output }) => ({ + target: 'writing', + context: { bullets: output.bullets }, + }), + }, + writing: { + schemas: { output: draftSchema }, + invoke: async ({ context }) => + (options.write + ?? (({ topic, bullets }) => + generateExampleObject({ + schema: draftSchema, + system: 'You turn research bullets into a short coherent draft.', + prompt: [ + `Topic: ${topic}`, + 'Use these bullets to write a short draft:', + ...bullets.map((bullet) => `- ${bullet}`), + ].join('\n'), + })))({ + topic: context.topic, + bullets: context.bullets, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + bullets: context.bullets, + draft: context.draft, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Topic'); + const machine = createSubflowExample(); + const result = await machine.execute(machine.getInitialState({ topic })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/supervisor.ts b/examples/supervisor.ts new file mode 100644 index 0000000..d54887b --- /dev/null +++ b/examples/supervisor.ts @@ -0,0 +1,251 @@ +import { z } from 'zod'; +import { + createAgentMachine, + decide, + decideResultSchema, + type DecideAdapter, +} from '../src/index.js'; +import { + closePrompt, + createOpenAiDecisionAdapter, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; + +const handlingParamsSchema = z.object({ + attempt: z.number().int().min(1), + instruction: z.string().nullable().optional(), +}); + +const workerResultSchema = z.discriminatedUnion('status', [ + z.object({ + status: z.literal('resolved'), + response: z.string(), + }), + z.object({ + status: z.literal('blocked'), + issue: z.string(), + }), +]); + +const supervisorOptions = { + retry: { + description: 'Retry the worker with a concrete instruction for the next attempt.', + schema: z.object({ + instruction: z.string(), + }), + }, + escalate: { + description: 'Escalate the task to a human or specialist owner.', + schema: z.object({ + reason: z.string(), + }), + }, +} as const; + +export function createSupervisorExample( + options: { + adapter?: DecideAdapter; + handle?: (args: { + request: string; + attempt: number; + instruction: string | null; + priorIssues: string[]; + }) => Promise>; + maxAttempts?: number; + } = {} +) { + const adapter = options.adapter ?? createOpenAiDecisionAdapter(); + const maxAttempts = options.maxAttempts ?? 2; + const handle = + options.handle ?? + ((args: { + request: string; + attempt: number; + instruction: string | null; + priorIssues: string[]; + }) => + generateExampleObject({ + schema: workerResultSchema, + system: [ + 'You are an operations worker handling a support request.', + 'Resolve the request when you have enough information.', + 'Return status="blocked" with a concise issue when the request cannot be completed yet.', + ].join('\n'), + prompt: [ + `Request: ${args.request}`, + `Attempt: ${args.attempt}`, + args.instruction + ? `Supervisor instruction: ${args.instruction}` + : 'Supervisor instruction: none', + args.priorIssues.length + ? `Prior issues:\n${args.priorIssues.map((issue, index) => `${index + 1}. ${issue}`).join('\n')}` + : 'Prior issues: none', + ].join('\n'), + })); + + return createAgentMachine({ + id: 'supervisor-example', + schemas: { + input: z.object({ + request: z.string(), + }), + output: z.object({ + request: z.string(), + status: z.enum(['resolved', 'escalated']), + resolution: z.string().nullable(), + escalationReason: z.string().nullable(), + attemptCount: z.number().int().min(0), + history: z.array(z.string()), + }), + }, + context: (input) => ({ + request: input.request, + attemptCount: 0, + latestIssue: null as string | null, + resolution: null as string | null, + escalationReason: null as string | null, + history: [] as string[], + priorIssues: [] as string[], + }), + initial: ({ context }) => ({ + target: 'handling', + input: { + attempt: 1, + instruction: null, + }, + context, + }), + states: { + handling: { + schemas: { input: handlingParamsSchema, output: workerResultSchema }, + invoke: async ({ context, input }) => + handle({ + request: context.request, + attempt: input.attempt, + instruction: input.instruction ?? null, + priorIssues: context.priorIssues, + }), + onDone: ({ output, context, }) => { + const nextAttemptCount = context.attemptCount + 1; + + if (output.status === 'resolved') { + return { + target: 'done', + context: { + attemptCount: nextAttemptCount, + resolution: output.response, + history: [ + ...context.history, + `worker:${nextAttemptCount}:resolved:${output.response}`, + ], + }, + }; + } + + return { + target: 'supervising', + context: { + attemptCount: nextAttemptCount, + latestIssue: output.issue, + priorIssues: [...context.priorIssues, output.issue], + history: [ + ...context.history, + `worker:${nextAttemptCount}:blocked:${output.issue}`, + ], + }, + }; + }, + }, + supervising: { + schemas: { output: decideResultSchema(supervisorOptions) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'openai/gpt-5.4-nano', + prompt: [ + 'You supervise a worker that may need retries or escalation.', + `Max attempts: ${maxAttempts}`, + `Completed attempts: ${context.attemptCount}`, + '', + `Request: ${context.request}`, + `Latest issue: ${context.latestIssue ?? 'none'}`, + context.history.length + ? `History:\n${context.history.map((entry, index) => `${index + 1}. ${entry}`).join('\n')}` + : 'History: none', + '', + context.attemptCount >= maxAttempts + ? 'You should normally escalate because the worker has reached the attempt limit.' + : 'Retry only if a concrete next instruction could unblock the worker.', + ].join('\n'), + options: supervisorOptions, + }), + onDone: ({ output, context }) => { + if (output.choice === 'retry') { + const instruction = + output.data.instruction + ?? 'Retry once with a more concrete plan and any available context.'; + + return { + target: 'handling', + context: { + history: [ + ...context.history, + `supervisor:retry:${instruction}`, + ], + }, + input: { + attempt: context.attemptCount + 1, + instruction, + }, + }; + } + + const reason = + output.data.reason + ?? `Escalated after ${context.attemptCount} unsuccessful attempts.`; + + return { + target: 'done', + context: { + escalationReason: reason, + history: [ + ...context.history, + `supervisor:escalate:${reason}`, + ], + }, + }; + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + request: context.request, + status: context.resolution ? ('resolved' as const) : ('escalated' as const), + resolution: context.resolution, + escalationReason: context.escalationReason, + attemptCount: context.attemptCount, + history: context.history, + }), + }, + }, + }); +} + +async function main() { + try { + const request = await prompt('Request'); + const machine = createSupervisorExample(); + console.log( + formatResult(await machine.execute(machine.getInitialState({ request }))) + ); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/tool-calling.ts b/examples/tool-calling.ts new file mode 100644 index 0000000..2753b17 --- /dev/null +++ b/examples/tool-calling.ts @@ -0,0 +1,141 @@ +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../src/index.js'; +import { + closePrompt, + generateExampleObject, + isMain, + prompt, + waitForRunDone, +} from './_run.js'; + +const forecastSchema = z.object({ + forecast: z.string(), +}); + +const toolProgressSchema = z.object({ + toolName: z.string(), + message: z.string(), + step: z.number().int().min(1), +}); + +export function createToolCallingExample( + getWeather: ( + city: string, + emitProgress: (event: z.infer) => void + ) => Promise> = async ( + city, + emitProgress + ) => { + emitProgress({ + toolName: 'getWeather', + message: `Looking up current conditions for ${city}.`, + step: 1, + }); + emitProgress({ + toolName: 'getWeather', + message: `Formatting the forecast for ${city}.`, + step: 2, + }); + + return generateExampleObject({ + schema: forecastSchema, + system: 'You generate plausible demo weather forecasts.', + prompt: `Return a short weather forecast for ${city}.`, + }); + } +) { + return createAgentMachine({ + id: 'tool-calling-example', + schemas: { + input: z.object({ city: z.string() }), + output: z.object({ forecast: z.string().nullable() }), + emitted: { + toolCall: z.object({ + toolName: z.string(), + input: z.object({ city: z.string() }), + }), + toolProgress: toolProgressSchema, + toolResult: z.object({ + toolName: z.string(), + output: forecastSchema, + }), + }, + }, + context: (input) => ({ + city: input.city, + forecast: null as string | null, + }), + initial: 'checkingWeather', + states: { + checkingWeather: { + schemas: { output: forecastSchema }, + invoke: async ({ context }, enq) => { + enq.emit({ + type: 'toolCall', + toolName: 'getWeather', + input: { city: context.city }, + }); + + const output = await getWeather(context.city, (progress) => { + enq.emit({ + type: 'toolProgress', + ...progress, + }); + }); + + enq.emit({ + type: 'toolResult', + toolName: 'getWeather', + output, + }); + + return output; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { forecast: output.forecast }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ forecast: context.forecast }), + }, + }, + }); +} + +async function main() { + try { + const city = await prompt('City'); + const machine = createToolCallingExample(); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { city }, + }); + + run.on('toolCall', (event) => { + console.log(`Calling ${event.toolName}(${event.input.city})`); + }); + + run.on('toolProgress', (event) => { + console.log(`${event.toolName} [${event.step}] ${event.message}`); + }); + + run.on('toolResult', (event) => { + console.log(`${event.toolName} -> ${event.output.forecast}`); + }); + + const done = await waitForRunDone(run); + console.log(done.output); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/tutor.ts b/examples/tutor.ts index 3a11f6b..9ef2009 100644 --- a/examples/tutor.ts +++ b/examples/tutor.ts @@ -1,100 +1,104 @@ -import { assign, createActor, log, setup } from 'xstate'; -import { fromTerminal } from './helpers/helpers'; -import { createAgent, fromDecision } from '../src'; import { z } from 'zod'; -import { openai } from '@ai-sdk/openai'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + isMain, + prompt, +} from './_run.js'; -const agent = createAgent({ - name: 'tutor', - model: openai('gpt-4-1106-preview'), - events: { - teach: z.object({ - instruction: z - .string() - .describe( - 'The feedback to give the human, correcting any grammatical errors, misspellings, etc.' - ), - }), - respond: z.object({ - response: z.string().describe('The response to the human in Spanish'), - }), - }, - system: - 'You are an expert Spanish tutor. You will respond to the human in Spanish.', +const feedbackSchema = z.object({ + instruction: z.string(), +}); + +const responseSchema = z.object({ + response: z.string(), }); -const machine = setup({ - types: { - context: {} as { - conversation: string[]; +export function createTutorExample( + options: { + teach?: (message: string) => Promise>; + respond?: (message: string) => Promise>; + } = {} +) { + const teach = + options.teach ?? + ((message: string) => + generateExampleObject({ + schema: feedbackSchema, + system: 'You are a Spanish tutor giving concise corrective feedback in English.', + prompt: `Give one short piece of coaching feedback for this learner message: ${message}`, + })); + const respond = + options.respond ?? + ((message: string) => + generateExampleObject({ + schema: responseSchema, + system: 'You are a friendly Spanish tutor. Reply in simple Spanish.', + prompt: `Respond to this learner message in simple Spanish and keep the conversation going: ${message}`, + })); + + return createAgentMachine({ + id: 'tutor-example', + schemas: { + input: z.object({ message: z.string() }), + output: z.object({ + conversation: z.array(z.string()), + feedback: z.string().nullable(), + response: z.string().nullable(), + }), }, - events: agent.types.events, - }, - actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, -}).createMachine({ - initial: 'human', - context: { - conversation: [], - }, - states: { - human: { - invoke: { - src: 'getFromTerminal', - input: 'Say something in Spanish:', - onDone: { - actions: assign({ - conversation: ({ context, event }) => - context.conversation.concat(`User: ` + event.output), - }), - target: 'ai', - }, + context: (input) => ({ + conversation: [`User: ${input.message}`], + feedback: null as string | null, + response: null as string | null, + }), + initial: 'teaching', + states: { + teaching: { + schemas: { output: feedbackSchema }, + invoke: async ({ context }) => + teach(context.conversation.at(-1)?.replace(/^User:\s*/, '') ?? ''), + onDone: ({ output }) => ({ + target: 'responding', + context: { feedback: output.instruction }, + }), }, - }, - ai: { - initial: 'teaching', - states: { - teaching: { - invoke: { - src: 'agent', - input: () => ({ - context: true, - goal: 'Give brief feedback to the human based on the most recent response of the conversation', - maxTokens: 100, - }), + responding: { + schemas: { output: responseSchema }, + invoke: async ({ context }) => + respond(context.conversation.at(-1)?.replace(/^User:\s*/, '') ?? ''), + onDone: ({ output, context }) => ({ + target: 'done', + context: { + response: output.response, + conversation: [...context.conversation, `Tutor: ${output.response}`], }, - on: { - teach: { - actions: ({ event }) => console.log(event.instruction), - target: 'responding', - }, - }, - }, - responding: { - invoke: { - src: 'agent', - input: () => ({ - context: true, - goal: 'Respond to the last message of the conversation in Spanish', - }), - }, - on: { - respond: { - actions: [ - assign({ - conversation: ({ context, event }) => - context.conversation.concat(`Agent: ` + event.response), - }), - log(({ event }) => event.response), - ], - target: 'done', - }, - }, - }, - done: { type: 'final' }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + conversation: context.conversation, + feedback: context.feedback, + response: context.response, + }), }, - onDone: { target: 'human' }, }, - }, -}); + }); +} + +async function main() { + try { + const message = await prompt('Say something in Spanish'); + const machine = createTutorExample(); + console.log(formatResult(await machine.execute(machine.getInitialState({ message })))); + } finally { + closePrompt(); + } +} -createActor(machine).start(); +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/workflow-guardrails.ts b/examples/workflow-guardrails.ts new file mode 100644 index 0000000..f67ada1 --- /dev/null +++ b/examples/workflow-guardrails.ts @@ -0,0 +1,363 @@ +import { z } from 'zod'; +import { createAgentMachine, type AgentAdapter } from '../src/index.js'; +import { closePrompt, formatResult, isMain, prompt } from './_run.js'; + +type WorkflowTool = (input?: Record) => Promise; + +const taskInputSchema = z.object({ task: z.string().optional() }).optional(); + +const planSchema = z.object({ plan: z.string() }); +const implementationSchema = z.object({ summary: z.string() }); +const testSchema = z.object({ + passed: z.boolean(), + output: z.string().optional(), +}); +const diagnosisSchema = z.object({ diagnosis: z.string() }); +const rootCauseSchema = z.object({ rootCause: z.string() }); +const proposalSchema = z.object({ proposal: z.string() }); +const fixSchema = z.object({ applied: z.boolean(), summary: z.string() }); +const verificationSchema = z.object({ + verified: z.boolean(), + summary: z.string(), +}); + +const readOnlyCodingTools = { + Read: async () => undefined, + Grep: async () => undefined, + Glob: async () => undefined, + LS: async () => undefined, + Bash: async () => undefined, +} satisfies Record; + +const editingCodingTools = { + ...readOnlyCodingTools, + Edit: async () => undefined, + Write: async () => undefined, +} satisfies Record; + +const incidentReadTools = { + Read: async () => undefined, + Bash: async () => undefined, + Grep: async () => undefined, +} satisfies Record; + +const incidentWriteTools = { + Read: async () => undefined, + Bash: async () => undefined, +} satisfies Record; + +const allIncidentTools = { + list_services: async () => undefined, + get_service: async () => undefined, + list_volumes: async () => undefined, + get_volume: async () => undefined, + get_logs: async () => undefined, + get_env: async () => undefined, + update_env: async () => undefined, + restart_service: async () => undefined, + delete_volume: async () => undefined, + delete_service: async () => undefined, + test_connection: async () => undefined, +} satisfies Record; + +function createSequenceAdapter(results: unknown[]): AgentAdapter { + let index = 0; + + return { + async generateText() { + const result = results[index] ?? results.at(-1); + index += 1; + return result; + }, + }; +} + +export function createGuardrailedBugfixWorkflowExample(options: { + adapter?: AgentAdapter; +} = {}) { + return createAgentMachine({ + id: 'guardrailed-bugfix-workflow', + schemas: { input: taskInputSchema }, + adapter: + options.adapter + ?? createSequenceAdapter([ + { plan: 'Read the failing test, inspect the implementation, then make the smallest fix.' }, + { summary: 'Applied a targeted code change.' }, + { passed: true, output: 'All tests passed.' }, + ]), + context: (input) => ({ + task: input?.task ?? 'Fix the failing tests.', + plan: null as string | null, + changeSummary: null as string | null, + testOutput: null as string | null, + }), + messages: (input) => [ + { role: 'user', content: input?.task ?? 'Fix the failing tests.' }, + ], + initial: 'planning', + states: { + planning: { + schemas: { output: planSchema }, + prompt: + 'Read relevant files and produce a brief fix plan. Do not edit anything yet.', + tools: readOnlyCodingTools, + onDone: ({ output }) => ({ + target: 'implementing', + context: { plan: output.plan }, + }), + }, + implementing: { + schemas: { output: implementationSchema }, + prompt: ({ snapshot }) => + [ + 'Implement the fix. Make targeted, minimal edits.', + `Current state: ${snapshot.value}`, + `Plan: ${snapshot.context.plan ?? 'none'}`, + ].join('\n'), + tools: editingCodingTools, + onDone: ({ output }) => ({ + target: 'testing', + context: { changeSummary: output.summary }, + }), + }, + testing: { + schemas: { output: testSchema }, + prompt: ({ snapshot }) => + [ + 'Run the tests to verify the fix.', + `Current state: ${snapshot.value}`, + `Change summary: ${snapshot.context.changeSummary ?? 'none'}`, + ].join('\n'), + tools: { + Read: readOnlyCodingTools.Read, + Bash: readOnlyCodingTools.Bash, + }, + onDone: ({ output }) => + output.passed + ? { + target: 'completed', + context: { testOutput: output.output ?? null }, + } + : { + target: 'implementing', + context: { testOutput: output.output ?? null }, + }, + }, + completed: { + type: 'final', + output: ({ context }) => ({ + plan: context.plan, + changeSummary: context.changeSummary, + testOutput: context.testOutput, + }), + }, + }, + }); +} + +export function createGuardrailedIncidentResponseExample(options: { + adapter?: AgentAdapter; +} = {}) { + return createAgentMachine({ + id: 'guardrailed-incident-response', + schemas: { + input: taskInputSchema, + events: { + APPROVED: z.object({ type: z.literal('APPROVED') }), + REJECTED: z.object({ type: z.literal('REJECTED') }), + }, + }, + adapter: + options.adapter + ?? createSequenceAdapter([ + { diagnosis: 'The web service cannot connect to its database.' }, + { rootCause: 'The staging database credential is stale.' }, + { proposal: 'Update the staging DB password and restart the web service.' }, + { applied: true, summary: 'Updated the staging DB password and restarted the service.' }, + { verified: true, summary: 'Connection test passed and service is healthy.' }, + ]), + context: (input) => ({ + task: + input?.task + ?? 'The staging environment is down. Diagnose and repair without destructive actions.', + diagnosis: null as string | null, + rootCause: null as string | null, + proposal: null as string | null, + fixSummary: null as string | null, + verification: null as string | null, + }), + messages: (input) => [ + { + role: 'user', + content: + input?.task + ?? 'The staging environment is down. Diagnose and repair without destructive actions.', + }, + ], + initial: 'diagnosing', + states: { + diagnosing: { + schemas: { output: diagnosisSchema }, + prompt: + 'Check service status and logs. Identify the likely failure. Do not modify anything.', + tools: { + ...incidentReadTools, + list_services: allIncidentTools.list_services, + get_service: allIncidentTools.get_service, + get_logs: allIncidentTools.get_logs, + get_volume: allIncidentTools.get_volume, + list_volumes: allIncidentTools.list_volumes, + }, + onDone: ({ output }) => ({ + target: 'investigating', + context: { diagnosis: output.diagnosis }, + }), + }, + investigating: { + schemas: { output: rootCauseSchema }, + prompt: + 'Investigate the root cause. Check environment variables, test connections, and read logs. Still read-only.', + tools: { + ...incidentReadTools, + get_env: allIncidentTools.get_env, + test_connection: allIncidentTools.test_connection, + get_logs: allIncidentTools.get_logs, + }, + onDone: ({ output }) => ({ + target: 'proposing', + context: { rootCause: output.rootCause }, + }), + }, + proposing: { + schemas: { output: proposalSchema }, + prompt: + 'Propose the fix. Describe exactly what should change and why. Do not execute the fix yet.', + tools: { Read: incidentReadTools.Read }, + onDone: ({ output }) => ({ + target: 'awaitingApproval', + context: { proposal: output.proposal }, + }), + }, + awaitingApproval: { + prompt: ({ snapshot }) => + [ + `Await approval while in ${snapshot.value}.`, + snapshot.context.proposal ?? '', + ].join('\n'), + tools: { Read: incidentReadTools.Read }, + on: { + APPROVED: { target: 'executingFix' }, + REJECTED: { target: 'proposing' }, + }, + }, + executingFix: { + schemas: { output: fixSchema }, + prompt: + 'Execute the approved fix. API actions allowed: update_env, restart_service. Do not delete volumes or services.', + tools: { + ...incidentWriteTools, + update_env: allIncidentTools.update_env, + restart_service: allIncidentTools.restart_service, + }, + onDone: ({ output }) => ({ + target: 'verifying', + context: { fixSummary: output.summary }, + }), + }, + verifying: { + schemas: { output: verificationSchema }, + prompt: + 'Verify the fix. Test the connection, check service status, and review logs.', + tools: { + ...incidentWriteTools, + test_connection: allIncidentTools.test_connection, + get_service: allIncidentTools.get_service, + get_logs: allIncidentTools.get_logs, + }, + onDone: ({ output }) => + output.verified + ? { + target: 'completed', + context: { verification: output.summary }, + } + : { + target: 'proposing', + context: { verification: output.summary }, + }, + }, + completed: { + type: 'final', + output: ({ context }) => ({ + diagnosis: context.diagnosis, + rootCause: context.rootCause, + proposal: context.proposal, + fixSummary: context.fixSummary, + verification: context.verification, + }), + }, + }, + }); +} + +export function createUnguardedIncidentResponseExample(options: { + adapter?: AgentAdapter; +} = {}) { + return createAgentMachine({ + id: 'unguarded-incident-response', + schemas: { input: taskInputSchema }, + adapter: + options.adapter + ?? createSequenceAdapter([ + { applied: true, summary: 'Used whatever API actions were available to repair the service.' }, + ]), + context: (input) => ({ + task: + input?.task + ?? 'The staging environment is down. Fix it with all API actions available.', + fixSummary: null as string | null, + }), + messages: (input) => [ + { + role: 'user', + content: + input?.task + ?? 'The staging environment is down. Fix it with all API actions available.', + }, + ], + initial: 'working', + states: { + working: { + schemas: { output: fixSchema }, + prompt: 'Fix the staging environment issue. All tools and API actions are available.', + tools: { + ...incidentReadTools, + ...allIncidentTools, + }, + onDone: ({ output }) => ({ + target: 'completed', + context: { fixSummary: output.summary }, + }), + }, + completed: { + type: 'final', + output: ({ context }) => ({ fixSummary: context.fixSummary }), + }, + }, + }); +} + +async function main() { + try { + const task = await prompt('Task'); + const machine = createGuardrailedBugfixWorkflowExample(); + const result = await machine.execute(machine.getInitialState({ task })); + + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/write-a-book-flow.ts b/examples/write-a-book-flow.ts new file mode 100644 index 0000000..f171020 --- /dev/null +++ b/examples/write-a-book-flow.ts @@ -0,0 +1,199 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../src/index.js'; +import { + closePrompt, + formatResult, + generateExampleObject, + generateExampleText, + isMain, + prompt, +} from './_run.js'; + +const chapterOutlineSchema = z.object({ + title: z.string(), + brief: z.string(), +}); + +const outlineSchema = z.object({ + title: z.string(), + chapters: z.array(chapterOutlineSchema), +}); + +const chapterSchema = z.object({ + title: z.string(), + content: z.string(), +}); + +const chapterBatchSchema = z.object({ + chapters: z.array(chapterSchema), +}); + +const manuscriptSchema = z.object({ + manuscript: z.string(), +}); + +type ChapterOutline = z.infer; + +export function createWriteABookFlowExample(options: { + createOutline?: (args: { + topic: string; + goal: string; + }) => Promise>; + writeChapter?: (args: { + title: string; + brief: string; + goal: string; + topic: string; + }) => Promise>; + compileManuscript?: (args: { + title: string; + chapters: z.infer[]; + }) => Promise>; +} = {}) { + const createOutline = + options.createOutline ?? + ((args: { topic: string; goal: string }) => + generateExampleObject({ + schema: outlineSchema, + system: 'Create a concise non-fiction book outline.', + prompt: [`Topic: ${args.topic}`, `Goal: ${args.goal}`].join('\n'), + })); + + const writeChapter = + options.writeChapter ?? + ((args: { + title: string; + brief: string; + goal: string; + topic: string; + }) => + generateExampleObject({ + schema: chapterSchema, + system: 'Write a concise but coherent book chapter.', + prompt: [ + `Book topic: ${args.topic}`, + `Book goal: ${args.goal}`, + `Chapter title: ${args.title}`, + `Chapter brief: ${args.brief}`, + ].join('\n'), + })); + + const compileManuscript = + options.compileManuscript ?? + ((args: { title: string; chapters: z.infer[] }) => + generateExampleObject({ + schema: manuscriptSchema, + system: 'Compile chapters into a single clean markdown manuscript.', + prompt: [ + `Title: ${args.title}`, + '', + ...args.chapters.map( + (chapter) => `## ${chapter.title}\n\n${chapter.content}` + ), + ].join('\n'), + })); + + return createAgentMachine({ + id: 'write-a-book-flow-example', + schemas: { + input: z.object({ + topic: z.string(), + goal: z.string(), + }), + output: z.object({ + title: z.string().nullable(), + outline: z.array(chapterOutlineSchema), + chapters: z.array(chapterSchema), + manuscript: z.string().nullable(), + }), + }, + context: (input) => ({ + topic: input.topic, + goal: input.goal, + title: null as string | null, + outline: [] as ChapterOutline[], + chapters: [] as z.infer[], + manuscript: null as string | null, + }), + initial: 'outlining', + states: { + outlining: { + schemas: { output: outlineSchema }, + invoke: async ({ context }) => + createOutline({ + topic: context.topic, + goal: context.goal, + }), + onDone: ({ output }) => ({ + target: 'writing', + context: { + title: output.title, + outline: output.chapters, + }, + }), + }, + writing: { + schemas: { output: chapterBatchSchema }, + invoke: async ({ context }) => { + const chapters = await Promise.all( + context.outline.map((chapter) => + writeChapter({ + title: chapter.title, + brief: chapter.brief, + goal: context.goal, + topic: context.topic, + }) + ) + ); + + return { chapters }; + }, + onDone: ({ output }) => ({ + target: 'compiling', + context: { + chapters: output.chapters, + }, + }), + }, + compiling: { + schemas: { output: manuscriptSchema }, + invoke: async ({ context }) => + compileManuscript({ + title: context.title ?? 'Untitled Book', + chapters: context.chapters, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { + manuscript: output.manuscript, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + title: context.title, + outline: context.outline, + chapters: context.chapters, + manuscript: context.manuscript, + }), + }, + }, + }); +} + +async function main() { + try { + const topic = await prompt('Book topic'); + const goal = await prompt('Book goal'); + const machine = createWriteABookFlowExample(); + const result = await machine.execute(machine.getInitialState({ topic, goal })); + console.log(formatResult(result)); + } finally { + closePrompt(); + } +} + +if (isMain(import.meta.url)) { + void main(); +} diff --git a/examples/chatbot-alt.ts b/examples_old/chatbot-alt.ts similarity index 100% rename from examples/chatbot-alt.ts rename to examples_old/chatbot-alt.ts diff --git a/examples_old/chatbot.ts b/examples_old/chatbot.ts new file mode 100644 index 0000000..122f664 --- /dev/null +++ b/examples_old/chatbot.ts @@ -0,0 +1,71 @@ +import { z } from 'zod'; +import { createAgent, fromDecision } from '../src'; +import { openai } from '@ai-sdk/openai'; +import { assign, createActor, log, setup } from 'xstate'; +import { fromTerminal } from './helpers/helpers'; + +const agent = createAgent({ + name: 'chatbot', + model: openai('gpt-4-turbo'), + events: { + 'agent.respond': z.object({ + response: z.string().describe('The response from the agent'), + }), + 'agent.endConversation': z.object({}).describe('Stop the conversation'), + }, + context: { + userMessage: z.string(), + }, +}); + +const machine = setup({ + types: agent.types, + actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, +}).createMachine({ + initial: 'listening', + context: { + userMessage: '', + }, + states: { + listening: { + invoke: { + src: 'getFromTerminal', + input: 'User:', + onDone: { + actions: assign({ + userMessage: ({ event }) => event.output, + }), + target: 'responding', + }, + }, + }, + responding: { + invoke: { + src: 'agent', + input: ({ context }) => ({ + context: { + userMessage: 'User says: ' + context.userMessage, + }, + messages: agent.getMessages(), + goal: 'Respond to the user, unless they want to end the conversation.', + }), + }, + on: { + 'agent.respond': { + actions: log(({ event }) => `Agent: ${event.response}`), + target: 'listening', + }, + 'agent.endConversation': 'finished', + }, + }, + finished: { + type: 'final', + }, + }, + exit: () => { + console.log('End of conversation.'); + process.exit(); + }, +}); + +createActor(machine).start(); diff --git a/examples/cot.ts b/examples_old/cot.ts similarity index 100% rename from examples/cot.ts rename to examples_old/cot.ts diff --git a/examples_old/email.ts b/examples_old/email.ts new file mode 100644 index 0000000..e65a88c --- /dev/null +++ b/examples_old/email.ts @@ -0,0 +1,118 @@ +import { z } from 'zod'; +import { createAgent, fromDecision } from '../src'; +import { openai } from '@ai-sdk/openai'; +import { assign, createActor, setup } from 'xstate'; +import { fromTerminal } from './helpers/helpers'; + +const agent = createAgent({ + name: 'email', + model: openai('gpt-4'), + events: { + askForClarification: z.object({ + questions: z.array(z.string()).describe('The questions to ask the agent'), + }), + submitEmail: z.object({ + email: z.string().describe('The email to submit'), + }), + }, +}); + +const machine = setup({ + types: { + events: agent.types.events, + input: {} as { + email: string; + instructions: string; + }, + context: {} as { + email: string; + instructions: string; + clarifications: string[]; + replyEmail: string | null; + }, + }, + actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, +}).createMachine({ + initial: 'checking', + context: ({ input }) => ({ + email: input.email, + instructions: input.instructions, + clarifications: [], + replyEmail: null, + }), + states: { + checking: { + invoke: { + src: 'agent', + input: ({ context }) => ({ + context: { + email: context.email, + instructions: context.instructions, + clarifications: context.clarifications, + }, + messages: agent.getMessages(), + goal: 'Respond to the email given the instructions and the provided clarifications. If not enough information is provided, ask for clarification. Otherwise, if you are absolutely sure that there is no ambiguous or missing information, create and submit a response email.', + }), + }, + on: { + askForClarification: { + actions: ({ event }) => console.log(event.questions.join('\n')), + target: 'clarifying', + }, + submitEmail: { + target: 'submitting', + }, + }, + }, + clarifying: { + invoke: { + src: 'getFromTerminal', + input: `Please provide answers to the questions above`, + onDone: { + actions: assign({ + clarifications: ({ context, event }) => + context.clarifications.concat(event.output), + }), + target: 'checking', + }, + }, + }, + submitting: { + invoke: { + src: 'agent', + input: ({ context }) => ({ + context: { + email: context.email, + instructions: context.instructions, + clarifications: context.clarifications, + }, + goal: `Create and submit an email based on the instructions.`, + }), + }, + on: { + submitEmail: { + actions: assign({ + replyEmail: ({ event }) => event.email, + }), + target: 'done', + }, + }, + }, + done: { + type: 'final', + entry: ({ context }) => console.log(context.replyEmail), + }, + }, + exit: () => { + console.log('End of conversation.'); + process.exit(); + }, +}); + +createActor(machine, { + input: { + email: 'That sounds great! When are you available?', + instructions: + 'Tell them exactly when I am available. Address them by his full (first and last) name.', + }, +}).start(); diff --git a/examples_old/email2.ts b/examples_old/email2.ts new file mode 100644 index 0000000..31ac3f5 --- /dev/null +++ b/examples_old/email2.ts @@ -0,0 +1,59 @@ +import { setup, SnapshotFrom } from 'xstate'; +import { mapState } from '../src/mapState'; + +const machine = setup({}).createMachine({ + initial: 'checking', + states: { + checking: { + on: { + askForClarification: { + target: 'clarifying', + }, + submitEmail: { + target: 'submitting', + }, + }, + }, + clarifying: { + on: { + provideClarification: { + target: 'checking', + }, + }, + }, + submitting: { + on: { + confirm: { + target: 'done', + }, + }, + }, + done: { + type: 'final', + }, + }, +}); + +function getStuff(snapshot: SnapshotFrom) { + return mapState< + typeof snapshot, + { + goal: string; + } + >(snapshot, { + states: { + checking: { + map: () => ({ + goal: 'Respond to the email given the instructions and the provided clarifications. If not enough information is provided, ask for clarification. Otherwise, if you are absolutely sure that there is no ambiguous or missing information, create and submit a response email.', + }), + }, + submitting: { + map: () => ({ + goal: 'Create and submit an email based on the instructions.', + }), + }, + }, + }); +} + +async function main() {} diff --git a/examples/example.ts b/examples_old/example.ts similarity index 100% rename from examples/example.ts rename to examples_old/example.ts diff --git a/examples/executor.ts b/examples_old/executor.ts similarity index 97% rename from examples/executor.ts rename to examples_old/executor.ts index b56e36c..bf56fbe 100644 --- a/examples/executor.ts +++ b/examples_old/executor.ts @@ -5,7 +5,7 @@ import { z } from 'zod'; import { fromTerminal } from './helpers/helpers'; const agent = createAgent({ - model: openai('gpt-4o-mini'), + model: openai('gpt-5.4-nano'), events: { getTime: z.object({}).describe('Get the current time'), other: z.object({}).describe('Do something else'), diff --git a/examples/goal.ts b/examples_old/goal.ts similarity index 100% rename from examples/goal.ts rename to examples_old/goal.ts diff --git a/examples/helpers/helpers.ts b/examples_old/helpers/helpers.ts similarity index 100% rename from examples/helpers/helpers.ts rename to examples_old/helpers/helpers.ts diff --git a/examples/helpers/loader.ts b/examples_old/helpers/loader.ts similarity index 100% rename from examples/helpers/loader.ts rename to examples_old/helpers/loader.ts diff --git a/examples/helpers/runner.ts b/examples_old/helpers/runner.ts similarity index 100% rename from examples/helpers/runner.ts rename to examples_old/helpers/runner.ts diff --git a/examples_old/hono/.gitignore b/examples_old/hono/.gitignore new file mode 100644 index 0000000..c363919 --- /dev/null +++ b/examples_old/hono/.gitignore @@ -0,0 +1,34 @@ +# prod +dist/ +dist-server/ + +# dev +.yarn/ +!.yarn/releases +.vscode/* +!.vscode/launch.json +!.vscode/*.code-snippets +.idea/workspace.xml +.idea/usage.statistics.xml +.idea/shelf + +# deps +node_modules/ +.wrangler + +# env +.env +.env.production +.dev.vars + +# logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# misc +.DS_Store diff --git a/examples_old/hono/README.md b/examples_old/hono/README.md new file mode 100644 index 0000000..eba2b1e --- /dev/null +++ b/examples_old/hono/README.md @@ -0,0 +1,21 @@ +```txt +npm install +npm run dev +``` + +```txt +npm run deploy +``` + +[For generating/synchronizing types based on your Worker configuration run](https://developers.cloudflare.com/workers/wrangler/commands/#types): + +```txt +npm run cf-typegen +``` + +Pass the `CloudflareBindings` as generics when instantiation `Hono`: + +```ts +// src/index.ts +const app = new Hono<{ Bindings: CloudflareBindings }>() +``` diff --git a/examples_old/hono/package.json b/examples_old/hono/package.json new file mode 100644 index 0000000..8bd9148 --- /dev/null +++ b/examples_old/hono/package.json @@ -0,0 +1,23 @@ +{ + "name": "hono", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "$npm_execpath run build && vite preview", + "deploy": "$npm_execpath run build && wrangler deploy", + "cf-typegen": "wrangler types --env-interface CloudflareBindings" + }, + "dependencies": { + "@ai-sdk/openai": "^3.0.24", + "ai": "^6.0.66", + "hono": "^4.11.7", + "xstate": "^5.26.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "^1.2.3", + "vite": "^6.3.5", + "wrangler": "^4.17.0" + } +} \ No newline at end of file diff --git a/examples_old/hono/pnpm-lock.yaml b/examples_old/hono/pnpm-lock.yaml new file mode 100644 index 0000000..77d37c1 --- /dev/null +++ b/examples_old/hono/pnpm-lock.yaml @@ -0,0 +1,1643 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ai-sdk/openai': + specifier: ^3.0.24 + version: 3.0.24(zod@3.25.76) + ai: + specifier: ^6.0.66 + version: 6.0.66(zod@3.25.76) + hono: + specifier: ^4.11.7 + version: 4.11.7 + xstate: + specifier: ^5.26.0 + version: 5.26.0 + zod: + specifier: ^3.23.0 + version: 3.25.76 + devDependencies: + '@cloudflare/vite-plugin': + specifier: ^1.2.3 + version: 1.22.1(vite@6.4.1)(workerd@1.20260128.0)(wrangler@4.61.1) + vite: + specifier: ^6.3.5 + version: 6.4.1 + wrangler: + specifier: ^4.17.0 + version: 4.61.1 + +packages: + + '@ai-sdk/gateway@3.0.31': + resolution: {integrity: sha512-WActnxPeW46XcfZWWEcJ1FytpjCtKQEo25WZVa2xZSf+u2FgSNVt/dXIvlSZetPnXo6T2P/GhFAPBULMN6siRA==, tarball: https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.31.tgz} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai@3.0.24': + resolution: {integrity: sha512-f4d2z4cQpaLnCxlhL5X+/FIpA7u55eYbfCtu7hJxukav7MIQi+5uufy5OAXdCieqPnsdoiGRWaI+VTPh151mZQ==, tarball: https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.24.tgz} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.12': + resolution: {integrity: sha512-sdC3eUTa5W4r/bISlF3nxmM6zc8mV7Nj3mWI9iUO0cib70h0Zr52Tz5gGzO6HcDirbKVTR2ywmZb61MHU68prA==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.12.tgz} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.6': + resolution: {integrity: sha512-hSfoJtLtpMd7YxKM+iTqlJ0ZB+kJ83WESMiWuWrNVey3X8gg97x0OdAAaeAeclZByCX3UdPOTqhvJdK8qYA3ww==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.6.tgz} + engines: {node: '>=18'} + + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==, tarball: https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz} + engines: {node: '>=18.0.0'} + + '@cloudflare/unenv-preset@2.12.0': + resolution: {integrity: sha512-NK4vN+2Z/GbfGS4BamtbbVk1rcu5RmqaYGiyHJQrA09AoxdZPHDF3W/EhgI0YSK8p3vRo/VNCtbSJFPON7FWMQ==, tarball: https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.12.0.tgz} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: ^1.20260115.0 + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/vite-plugin@1.22.1': + resolution: {integrity: sha512-RDWc6WtrdjVDfpBeO3MYcgJIbq+Phg9qBXq1Ixl00qPqM8bgKp9oPLhg8oayynQs8udNnqkV0CjfojvIhhfZWg==, tarball: https://registry.npmjs.org/@cloudflare/vite-plugin/-/vite-plugin-1.22.1.tgz} + peerDependencies: + vite: ^6.1.0 || ^7.0.0 + wrangler: ^4.61.1 + + '@cloudflare/workerd-darwin-64@1.20260128.0': + resolution: {integrity: sha512-XJN8zWWNG3JwAUqqwMLNKJ9fZfdlQkx/zTTHW/BB8wHat9LjKD6AzxqCu432YmfjR+NxEKCzUOxMu1YOxlVxmg==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260128.0.tgz} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260128.0': + resolution: {integrity: sha512-vKnRcmnm402GQ5DOdfT5H34qeR2m07nhnTtky8mTkNWP+7xmkz32AMdclwMmfO/iX9ncyKwSqmml2wPG32eq/w==, tarball: https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260128.0.tgz} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260128.0': + resolution: {integrity: sha512-RiaR+Qugof/c6oI5SagD2J5wJmIfI8wQWaV2Y9905Raj6sAYOFaEKfzkKnoLLLNYb4NlXicBrffJi1j7R/ypUA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260128.0.tgz} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260128.0': + resolution: {integrity: sha512-U39U9vcXLXYDbrJ112Q7D0LDUUnM54oXfAxPgrL2goBwio7Z6RnsM25TRvm+Q06F4+FeDOC4D51JXlFHb9t1OA==, tarball: https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260128.0.tgz} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260128.0': + resolution: {integrity: sha512-fdJwSqRkJsAJFJ7+jy0th2uMO6fwaDA8Ny6+iFCssfzlNkc4dP/twXo+3F66FMLMe/6NIqjzVts0cpiv7ERYbQ==, tarball: https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260128.0.tgz} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, tarball: https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz} + engines: {node: '>=12'} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, tarball: https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==, tarball: https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, tarball: https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, tarball: https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, tarball: https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, tarball: https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, tarball: https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, tarball: https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, tarball: https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, tarball: https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, tarball: https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, tarball: https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, tarball: https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, tarball: https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, tarball: https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, tarball: https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, tarball: https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, tarball: https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, tarball: https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, tarball: https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, tarball: https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==, tarball: https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz} + engines: {node: '>=8.0.0'} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==, tarball: https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==, tarball: https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==, tarball: https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, tarball: https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, tarball: https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==, tarball: https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.14': + resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==, tarball: https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.14.tgz} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, tarball: https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, tarball: https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz} + + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==, tarball: https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz} + engines: {node: '>= 20'} + + ai@6.0.66: + resolution: {integrity: sha512-Klnzjlc3JczRykD75t+Qn5Jt5HwUCaLlN9aZku9KrSDjhc/pab54YH0w85huue7FLPlbTVF5zaQrw3NdEwiGpA==, tarball: https://registry.npmjs.org/ai/-/ai-6.0.66.tgz} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==, tarball: https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==, tarball: https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz} + engines: {node: '>=18'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, tarball: https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz} + engines: {node: '>=8'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==, tarball: https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz} + engines: {node: '>=18'} + hasBin: true + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==, tarball: https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz} + engines: {node: '>=18.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, tarball: https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + hono@4.11.7: + resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==, tarball: https://registry.npmjs.org/hono/-/hono-4.11.7.tgz} + engines: {node: '>=16.9.0'} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, tarball: https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, tarball: https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz} + engines: {node: '>=6'} + + miniflare@4.20260128.0: + resolution: {integrity: sha512-AVCn3vDRY+YXu1sP4mRn81ssno6VUqxo29uY2QVfgxXU2TMLvhRIoGwm7RglJ3Gzfuidit5R86CMQ6AvdFTGAw==, tarball: https://registry.npmjs.org/miniflare/-/miniflare-4.20260128.0.tgz} + engines: {node: '>=18.0.0'} + hasBin: true + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, tarball: https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, tarball: https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, tarball: https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, tarball: https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz} + engines: {node: '>=0.10.0'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==, tarball: https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, tarball: https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz} + + undici@7.18.2: + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==, tarball: https://registry.npmjs.org/undici/-/undici-7.18.2.tgz} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==, tarball: https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==, tarball: https://registry.npmjs.org/vite/-/vite-6.4.1.tgz} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + workerd@1.20260128.0: + resolution: {integrity: sha512-EhLJGptSGFi8AEErLiamO3PoGpbRqL+v4Ve36H2B38VxmDgFOSmDhfepBnA14sCQzGf1AEaoZX2DCwZsmO74yQ==, tarball: https://registry.npmjs.org/workerd/-/workerd-1.20260128.0.tgz} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.61.1: + resolution: {integrity: sha512-hfYQ16VLPkNi8xE1/V3052S2stM5e+vq3Idpt83sXoDC3R7R1CLgMkK6M6+Qp3G+9GVDNyHCkvohMPdfFTaD4Q==, tarball: https://registry.npmjs.org/wrangler/-/wrangler-4.61.1.tgz} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20260128.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==, tarball: https://registry.npmjs.org/ws/-/ws-8.18.0.tgz} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xstate@5.26.0: + resolution: {integrity: sha512-Fvi9VBoqHgsGYLU2NTag8xDTWtKqUC0+ue7EAhBNBb06wf620QEy05upBaEI1VLMzIn63zugLV8nHb69ZUWYAA==, tarball: https://registry.npmjs.org/xstate/-/xstate-5.26.0.tgz} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==, tarball: https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==, tarball: https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, tarball: https://registry.npmjs.org/zod/-/zod-3.25.76.tgz} + +snapshots: + + '@ai-sdk/gateway@3.0.31(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.6 + '@ai-sdk/provider-utils': 4.0.12(zod@3.25.76) + '@vercel/oidc': 3.1.0 + zod: 3.25.76 + + '@ai-sdk/openai@3.0.24(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.6 + '@ai-sdk/provider-utils': 4.0.12(zod@3.25.76) + zod: 3.25.76 + + '@ai-sdk/provider-utils@4.0.12(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.6 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 + + '@ai-sdk/provider@3.0.6': + dependencies: + json-schema: 0.4.0 + + '@cloudflare/kv-asset-handler@0.4.2': {} + + '@cloudflare/unenv-preset@2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260128.0)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260128.0 + + '@cloudflare/vite-plugin@1.22.1(vite@6.4.1)(workerd@1.20260128.0)(wrangler@4.61.1)': + dependencies: + '@cloudflare/unenv-preset': 2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260128.0) + miniflare: 4.20260128.0 + unenv: 2.0.0-rc.24 + vite: 6.4.1 + wrangler: 4.61.1 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - workerd + + '@cloudflare/workerd-darwin-64@1.20260128.0': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260128.0': + optional: true + + '@cloudflare/workerd-linux-64@1.20260128.0': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260128.0': + optional: true + + '@cloudflare/workerd-windows-64@1.20260128.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.0': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.0': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.0': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.0': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.0': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.0': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.0': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.0': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.0': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.0': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.0': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.0': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.0': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.0': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.0': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.0': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.0': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.0': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.0': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.0': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.0': + optional: true + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@opentelemetry/api@1.9.0': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.14': {} + + '@standard-schema/spec@1.1.0': {} + + '@types/estree@1.0.8': {} + + '@vercel/oidc@3.1.0': {} + + ai@6.0.66(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 3.0.31(zod@3.25.76) + '@ai-sdk/provider': 3.0.6 + '@ai-sdk/provider-utils': 4.0.12(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + zod: 3.25.76 + + blake3-wasm@2.1.5: {} + + cookie@1.1.1: {} + + detect-libc@2.1.2: {} + + error-stack-parser-es@1.0.5: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + + eventsource-parser@3.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fsevents@2.3.3: + optional: true + + hono@4.11.7: {} + + json-schema@0.4.0: {} + + kleur@4.1.5: {} + + miniflare@4.20260128.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.18.2 + workerd: 1.20260128.0 + ws: 8.18.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + nanoid@3.3.11: {} + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + semver@7.7.3: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + source-map-js@1.2.1: {} + + supports-color@10.2.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tslib@2.8.1: + optional: true + + undici@7.18.2: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + vite@6.4.1: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + workerd@1.20260128.0: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260128.0 + '@cloudflare/workerd-darwin-arm64': 1.20260128.0 + '@cloudflare/workerd-linux-64': 1.20260128.0 + '@cloudflare/workerd-linux-arm64': 1.20260128.0 + '@cloudflare/workerd-windows-64': 1.20260128.0 + + wrangler@4.61.1: + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@cloudflare/unenv-preset': 2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260128.0) + blake3-wasm: 2.1.5 + esbuild: 0.27.0 + miniflare: 4.20260128.0 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260128.0 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ws@8.18.0: {} + + xstate@5.26.0: {} + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.14 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod@3.25.76: {} diff --git a/examples_old/hono/public/.assetsignore b/examples_old/hono/public/.assetsignore new file mode 100644 index 0000000..9f1f131 --- /dev/null +++ b/examples_old/hono/public/.assetsignore @@ -0,0 +1 @@ +.vite \ No newline at end of file diff --git a/examples_old/hono/public/favicon.ico b/examples_old/hono/public/favicon.ico new file mode 100644 index 0000000..5431643 Binary files /dev/null and b/examples_old/hono/public/favicon.ico differ diff --git a/examples_old/hono/src/agent.ts b/examples_old/hono/src/agent.ts new file mode 100644 index 0000000..602c253 --- /dev/null +++ b/examples_old/hono/src/agent.ts @@ -0,0 +1,90 @@ +import { generateText, tool } from 'ai'; +import { createOpenAI } from '@ai-sdk/openai'; +import type { StateValue } from 'xstate'; +import { emailMachine } from './machine'; +import { agentEvents } from './events'; +import { getAllTransitions } from './utils'; +import { z } from 'zod'; + +type ObservedState = { + value: StateValue; + context: Record; +}; + +// States where agent makes decisions +export function requiresAgentDecision(stateValue: StateValue): boolean { + return stateValue === 'checking'; +} + +// Build AI SDK tools from Zod events, filtered by available transitions +function buildTools( + resolvedState: ReturnType +) { + const transitions = getAllTransitions(resolvedState); + const availableEventTypes = new Set(transitions.map((t) => t.eventType)); + + const tools: Record> = {}; + + for (const [eventType, schema] of Object.entries(agentEvents)) { + if (!availableEventTypes.has(eventType)) continue; + + tools[eventType] = tool({ + description: schema.description!, + inputSchema: schema, + execute: async (params) => ({ type: eventType, ...params }), + }); + } + + return tools; +} + +export async function getAgentDecision( + observedState: ObservedState, + goal: string, + apiKey: string +): Promise<{ type: string; [key: string]: unknown } | null> { + // Rehydrate state from serialized form + const resolvedState = emailMachine.resolveState(observedState); + const tools = buildTools(resolvedState); + + console.log('tools', tools); + + if (Object.keys(tools).length === 0) { + return null; + } + + const context = observedState.context as { + userRequest: string; + clarifications: string[]; + questions: string[]; + }; + + const systemPrompt = `You are an email assistant helping draft emails. + +User's request: ${context.userRequest} + +${ + context.clarifications.length > 0 + ? `Previous clarifications provided:\n${context.clarifications.join('\n')}` + : '' +} + +${goal} + +If you need more information to write a proper email (recipient, tone, specific details), ask for clarification. +If you have enough information, submit the email with recipient, subject, and body.`; + + const openai = createOpenAI({ apiKey }); + const result = await generateText({ + model: openai.chat('gpt-5-mini'), + system: systemPrompt, + messages: [{ role: 'user', content: goal }], + tools, + toolChoice: 'required', + }); + + const toolResult = result.toolResults[0]; + return ( + (toolResult?.result as { type: string; [key: string]: unknown }) ?? null + ); +} diff --git a/examples_old/hono/src/db.ts b/examples_old/hono/src/db.ts new file mode 100644 index 0000000..4aa0d30 --- /dev/null +++ b/examples_old/hono/src/db.ts @@ -0,0 +1,76 @@ +import type { StateValue } from 'xstate'; + +export interface StateEntry { + id: string; + value: StateValue; + context: Record; + event: { type: string; [key: string]: unknown } | null; + timestamp: number; +} + +export interface Session { + sessionId: string; + value: StateValue; + context: Record; + history: StateEntry[]; + createdAt: number; +} + +export interface SessionDB { + createSession(initialContext: Record): string; + getSession(sessionId: string): Session | null; + appendState( + sessionId: string, + entry: { + value: StateValue; + context: Record; + event: { type: string; [key: string]: unknown } | null; + } + ): void; +} + +// In-memory implementation +const sessions = new Map(); + +export const db: SessionDB = { + createSession(initialContext) { + const sessionId = crypto.randomUUID(); + const now = Date.now(); + const session: Session = { + sessionId, + value: 'checking', + context: initialContext, + history: [ + { + id: crypto.randomUUID(), + value: 'checking', + context: initialContext, + event: null, + timestamp: now, + }, + ], + createdAt: now, + }; + sessions.set(sessionId, session); + return sessionId; + }, + + getSession(sessionId) { + return sessions.get(sessionId) ?? null; + }, + + appendState(sessionId, entry) { + const session = sessions.get(sessionId); + if (!session) throw new Error('Session not found'); + + const stateEntry: StateEntry = { + id: crypto.randomUUID(), + timestamp: Date.now(), + ...entry, + }; + + session.history.push(stateEntry); + session.value = entry.value; + session.context = entry.context; + }, +}; diff --git a/examples_old/hono/src/events.ts b/examples_old/hono/src/events.ts new file mode 100644 index 0000000..b37788b --- /dev/null +++ b/examples_old/hono/src/events.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +// Events the agent can choose +export const agentEvents = { + askForClarification: z + .object({ + questions: z + .array(z.string()) + .describe('Questions to ask the user for clarification'), + }) + .describe('Ask the user for more information before drafting email'), + + submitEmail: z + .object({ + recipient: z.string().describe('Email recipient address'), + subject: z.string().describe('Email subject line'), + body: z.string().describe('Email body content'), + }) + .describe('Submit the final drafted email'), +}; + +// Events the user sends +export const userEvents = { + provideClarification: z.object({ + answers: z.string().describe('User answers to clarification questions'), + }), + + confirm: z.object({}).describe('Confirm and send the email'), +}; + +export type AgentEventType = keyof typeof agentEvents; +export type UserEventType = keyof typeof userEvents; diff --git a/examples_old/hono/src/index.ts b/examples_old/hono/src/index.ts new file mode 100644 index 0000000..09606ac --- /dev/null +++ b/examples_old/hono/src/index.ts @@ -0,0 +1,362 @@ +import { Hono } from 'hono'; +import { html, raw } from 'hono/html'; +import { emailMachine } from './machine'; +import { db } from './db'; +import { getAgentDecision, requiresAgentDecision } from './agent'; +import { transition } from 'xstate'; + +type Bindings = { + OPENAI_API_KEY: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +// GET / - Simple UI +app.get('/', async (c) => { + const sessionId = c.req.query('sessionId'); + let initialSession = null; + + if (sessionId) { + const session = db.getSession(sessionId); + if (session) { + initialSession = { + sessionId, + state: { value: session.value, context: session.context }, + }; + } + } + + return c.html(html` + + + + Email Agent + + + +

Email Agent

+ +
+ + +
+ + + + + + + `); +}); + +// POST /sessions - Start new email session +app.post('/sessions', async (c) => { + const body = await c.req.json<{ userRequest: string }>(); + + const sessionId = db.createSession({ + userRequest: body.userRequest, + recipient: '', + subject: '', + body: '', + clarifications: [], + questions: [], + }); + + const session = db.getSession(sessionId)!; + + const response: { + sessionId: string; + state: { value: unknown; context: Record }; + agentResponse?: { type: string; [key: string]: unknown }; + } = { + sessionId, + state: { value: session.value, context: session.context }, + }; + + // Initial state requires agent decision + if (requiresAgentDecision(session.value)) { + console.log('requiresAgentDecision', session.value); + const event = await getAgentDecision( + { value: session.value, context: session.context }, + 'Help the user draft and send an email based on their request.', + c.env.OPENAI_API_KEY + ); + + console.log('event', event); + + if (event) { + const resolvedState = emailMachine.resolveState({ + value: session.value, + context: session.context, + }); + const [nextState] = transition(emailMachine, resolvedState, event as any); + + console.log('nextState', nextState); + + db.appendState(sessionId, { + value: nextState.value, + context: nextState.context, + event, + }); + + response.state = { value: nextState.value, context: nextState.context }; + response.agentResponse = event; + } + } + + return c.json(response); +}); + +// POST /sessions/:id/events - Send event to session +app.post('/sessions/:id/events', async (c) => { + const sessionId = c.req.param('id'); + const session = db.getSession(sessionId); + + if (!session) { + return c.json({ error: 'Session not found' }, 404); + } + + const event = await c.req.json<{ type: string; [key: string]: unknown }>(); + + // Transition with user event + const resolvedState = emailMachine.resolveState({ + value: session.value, + context: session.context, + }); + const [nextState] = transition(emailMachine, resolvedState, event as any); + + db.appendState(sessionId, { + value: nextState.value, + context: nextState.context, + event, + }); + + const response: { + state: { value: unknown; context: Record }; + agentResponse?: { type: string; [key: string]: unknown }; + } = { + state: { value: nextState.value, context: nextState.context }, + }; + + // If new state requires agent, call LLM + if (requiresAgentDecision(nextState.value)) { + console.log('requiresAgentDecision', nextState.value); + const agentEvent = await getAgentDecision( + { value: nextState.value, context: nextState.context }, + 'Continue helping draft the email based on the clarifications provided.', + c.env.OPENAI_API_KEY + ); + + console.log('agentEvent', agentEvent); + + if (agentEvent) { + const [afterAgentState] = transition( + emailMachine, + emailMachine.resolveState({ + value: nextState.value, + context: nextState.context, + }), + agentEvent as any + ); + + console.log('afterAgentState', afterAgentState); + + db.appendState(sessionId, { + value: afterAgentState.value, + context: afterAgentState.context, + event: agentEvent, + }); + + response.state = { + value: afterAgentState.value, + context: afterAgentState.context, + }; + response.agentResponse = agentEvent; + } + } + + return c.json(response); +}); + +// GET /sessions/:id - Get current state +app.get('/sessions/:id', (c) => { + const sessionId = c.req.param('id'); + const session = db.getSession(sessionId); + + if (!session) { + return c.json({ error: 'Session not found' }, 404); + } + + return c.json({ + sessionId, + state: { value: session.value, context: session.context }, + }); +}); + +// GET /sessions/:id/history - Get full append-only history +app.get('/sessions/:id/history', (c) => { + const sessionId = c.req.param('id'); + const session = db.getSession(sessionId); + + if (!session) { + return c.json({ error: 'Session not found' }, 404); + } + + return c.json({ sessionId, history: session.history }); +}); + +export default app; diff --git a/examples_old/hono/src/machine.ts b/examples_old/hono/src/machine.ts new file mode 100644 index 0000000..bdcf230 --- /dev/null +++ b/examples_old/hono/src/machine.ts @@ -0,0 +1,74 @@ +import { setup, assign } from 'xstate'; + +export const emailMachine = setup({ + types: { + context: {} as { + userRequest: string; + recipient: string; + subject: string; + body: string; + clarifications: string[]; + questions: string[]; + }, + events: {} as + | { type: 'askForClarification'; questions: string[] } + | { type: 'provideClarification'; answers: string } + | { type: 'submitEmail'; recipient: string; subject: string; body: string } + | { type: 'confirm' }, + }, +}).createMachine({ + id: 'emailAgent', + initial: 'checking', + context: { + userRequest: '', + recipient: '', + subject: '', + body: '', + clarifications: [], + questions: [], + }, + states: { + checking: { + // Agent decides: askForClarification or submitEmail + on: { + askForClarification: { + target: 'clarifying', + actions: assign({ + questions: ({ event }) => event.questions, + }), + }, + submitEmail: { + target: 'submitting', + actions: assign({ + recipient: ({ event }) => event.recipient, + subject: ({ event }) => event.subject, + body: ({ event }) => event.body, + }), + }, + }, + }, + clarifying: { + // Wait for user clarification + on: { + provideClarification: { + target: 'checking', + actions: assign({ + clarifications: ({ context, event }) => [ + ...context.clarifications, + event.answers, + ], + }), + }, + }, + }, + submitting: { + // User confirms or edits + on: { + confirm: 'done', + }, + }, + done: { + type: 'final', + }, + }, +}); diff --git a/examples_old/hono/src/style.css b/examples_old/hono/src/style.css new file mode 100644 index 0000000..50969c8 --- /dev/null +++ b/examples_old/hono/src/style.css @@ -0,0 +1,3 @@ +h1 { + font-family: Arial, Helvetica, sans-serif; +} diff --git a/examples_old/hono/src/utils.ts b/examples_old/hono/src/utils.ts new file mode 100644 index 0000000..5d9636b --- /dev/null +++ b/examples_old/hono/src/utils.ts @@ -0,0 +1,29 @@ +import type { AnyMachineSnapshot, AnyStateNode } from 'xstate'; + +export interface TransitionData { + eventType: string; + description?: string; +} + +export function getAllTransitions(state: AnyMachineSnapshot): TransitionData[] { + const nodes = state._nodes; + const transitions = (nodes as AnyStateNode[]) + .map((node) => [...(node as AnyStateNode).transitions.values()]) + .map((nodeTransitions) => { + return nodeTransitions.map((nodeEventTransitions) => { + return nodeEventTransitions.map((transition) => ({ + eventType: transition.eventType, + description: transition.description, + })); + }); + }) + .flat(2); + + return transitions; +} + +export function randomId() { + const timestamp = Date.now().toString(36); + const random = Math.random().toString(36).substring(2, 9); + return timestamp + random; +} diff --git a/examples_old/hono/tsconfig.json b/examples_old/hono/tsconfig.json new file mode 100644 index 0000000..fe4b04f --- /dev/null +++ b/examples_old/hono/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "lib": [ + "ESNext" + ], + "types": ["vite/client"], + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx" + }, +} \ No newline at end of file diff --git a/examples_old/hono/vite.config.ts b/examples_old/hono/vite.config.ts new file mode 100644 index 0000000..f626b72 --- /dev/null +++ b/examples_old/hono/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from '@cloudflare/vite-plugin' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [cloudflare()] +}) diff --git a/examples_old/hono/wrangler.jsonc b/examples_old/hono/wrangler.jsonc new file mode 100644 index 0000000..8441ec8 --- /dev/null +++ b/examples_old/hono/wrangler.jsonc @@ -0,0 +1,6 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "hono", + "compatibility_date": "2025-08-03", + "main": "./src/index.ts" +} \ No newline at end of file diff --git a/examples_old/joke.ts b/examples_old/joke.ts new file mode 100644 index 0000000..e9ca1cd --- /dev/null +++ b/examples_old/joke.ts @@ -0,0 +1,227 @@ +import { assign, createActor, fromCallback, log, setup } from 'xstate'; +import { createAgent, fromDecision } from '../src'; +import { loadingAnimation } from './helpers/loader'; +import { z } from 'zod'; +import { openai } from '@ai-sdk/openai'; +import { fromTerminal } from './helpers/helpers'; + +export function getRandomFunnyPhrase() { + const funnyPhrases = [ + 'Concocting chuckles...', + 'Brewing belly laughs...', + 'Fabricating funnies...', + 'Assembling amusement...', + 'Molding merriment...', + 'Whipping up wisecracks...', + 'Generating guffaws...', + 'Inventing hilarity...', + 'Cultivating chortles...', + 'Hatching howlers...', + ]; + return funnyPhrases[Math.floor(Math.random() * funnyPhrases.length)]!; +} + +export function getRandomRatingPhrase() { + const ratingPhrases = [ + 'Assessing amusement...', + 'Evaluating hilarity...', + 'Ranking chuckles...', + 'Classifying cackles...', + 'Scoring snickers...', + 'Rating roars...', + 'Judging jollity...', + 'Measuring merriment...', + 'Rating rib-ticklers...', + ]; + return ratingPhrases[Math.floor(Math.random() * ratingPhrases.length)]!; +} + +const loader = fromCallback(({ input }: { input: string }) => { + const anim = loadingAnimation(input); + + return () => { + anim.stop(); + }; +}); + +const agent = createAgent({ + name: 'joke-teller', + model: openai('gpt-4-turbo'), + events: { + askForTopic: z.object({ + topic: z.string().describe('The topic for the joke'), + }), + 'agent.tellJoke': z.object({ + joke: z.string().describe('The joke text'), + }), + 'agent.endJokes': z.object({}).describe('End the jokes'), + 'agent.rateJoke': z.object({ + rating: z.number().min(1).max(10), + explanation: z.string(), + }), + 'agent.continue': z.object({}).describe('Continue'), + 'agent.markRelevancy': z.object({ + relevant: z.boolean().describe('Whether the joke was relevant'), + explanation: z + .string() + .describe('The explanation for why the joke was relevant or not'), + }), + }, + context: { + topic: z.string().describe('The topic for the joke'), + jokes: z.array(z.string()).describe('The jokes told so far'), + desire: z.string().nullable().describe('The user desire'), + lastRating: z.number().nullable().describe('The last joke rating'), + loader: z.string().nullable().describe('The loader text'), + }, +}); + +const jokeMachine = setup({ + types: agent.types, + actors: { + agent: fromDecision(agent), + loader, + getFromTerminal: fromTerminal, + }, +}).createMachine({ + id: 'joke', + context: () => ({ + topic: '', + jokes: [], + desire: null, + lastRating: null, + loader: null, + }), + initial: 'waitingForTopic', + states: { + waitingForTopic: { + invoke: { + src: 'getFromTerminal', + input: 'Give me a joke topic.', + onDone: { + actions: assign({ + topic: ({ event }) => event.output, + }), + target: 'tellingJoke', + }, + }, + }, + tellingJoke: { + invoke: [ + { + src: 'agent', + input: ({ context }) => ({ + context: { + topic: context.topic, + }, + goal: `Tell me a joke about the topic. Do not make any joke that is not relevant to the topic.`, + }), + }, + { + src: 'loader', + input: getRandomFunnyPhrase, + }, + ], + on: { + 'agent.tellJoke': { + actions: [ + assign({ + jokes: ({ context, event }) => [...context.jokes, event.joke], + }), + log(({ event }) => event.joke), + ], + target: 'relevance', + }, + }, + }, + relevance: { + invoke: { + src: 'agent', + input: ({ context }) => ({ + context: { + topic: context.topic, + lastJoke: context.jokes.at(-1), + }, + goal: 'An irrelevant joke has no reference to the topic. If the last joke is completely irrelevant to the topic, ask for a new joke topic. Otherwise, continue.', + }), + }, + on: { + 'agent.markRelevancy': [ + { + guard: ({ event }) => !event.relevant, + actions: log( + ({ event }) => 'Irrelevant joke: ' + event.explanation + ), + target: 'waitingForTopic', + description: 'Continue', + }, + { target: 'rateJoke' }, + ], + }, + }, + rateJoke: { + invoke: [ + { + src: 'agent', + input: ({ context }) => ({ + context: { + jokes: context.jokes, + }, + goal: `Rate the last joke on a scale of 1 to 10.`, + }), + }, + { + src: 'loader', + input: getRandomRatingPhrase, + }, + ], + on: { + 'agent.rateJoke': { + actions: [ + assign({ + lastRating: ({ event }) => event.rating, + }), + log( + ({ event }) => `Rating: ${event.rating}\n\n${event.explanation}` + ), + ], + target: 'decide', + }, + }, + }, + decide: { + invoke: { + src: 'agent', + input: ({ context }) => ({ + context: { + lastRating: context.lastRating, + }, + goal: `Choose what to do next, given the previous rating of the joke.`, + }), + }, + on: { + askForTopic: { + target: 'waitingForTopic', + actions: log("That joke wasn't good enough. Let's try again."), + description: + 'Ask for a new topic, because the last joke rated 6 or lower', + }, + 'agent.endJokes': { + target: 'end', + actions: log('That joke was good enough. Goodbye!'), + description: 'End the jokes, since the last joke rated 7 or higher', + }, + }, + }, + end: { + type: 'final', + }, + }, + exit: () => { + process.exit(); + }, +}); + +const actor = createActor(jokeMachine); + +actor.start(); diff --git a/examples/multi.ts b/examples_old/multi.ts similarity index 98% rename from examples/multi.ts rename to examples_old/multi.ts index b82971b..1b5b74e 100644 --- a/examples/multi.ts +++ b/examples_old/multi.ts @@ -6,7 +6,7 @@ import { openai } from '@ai-sdk/openai'; const agent = createAgent({ name: 'multi', - model: openai('gpt-4o-mini'), + model: openai('gpt-5.4-nano'), events: { 'agent.respond': z.object({ response: z.string().describe('The response from the agent'), diff --git a/examples_old/newspaper.ts b/examples_old/newspaper.ts new file mode 100644 index 0000000..3671a25 --- /dev/null +++ b/examples_old/newspaper.ts @@ -0,0 +1,324 @@ +// Based on GPT Newspaper: +// https://github.com/assafelovic/gpt-newspaper +// https://gist.github.com/TheGreatBonnie/58dc21ebbeeb8cbb08df665db762738c + +import { TavilySearchAPIRetriever } from '@langchain/community/retrievers/tavily_search_api'; +import { ChatOpenAI } from '@langchain/openai'; +import { HumanMessage, SystemMessage } from '@langchain/core/messages'; +import { assign, createActor, fromPromise, setup } from 'xstate'; + +interface AgentState { + topic: string; + searchResults?: string; + article?: string; + critique?: string; + revisionCount: number; +} + +function model() { + return new ChatOpenAI({ + temperature: 0, + modelName: 'gpt-4-1106-preview', + openAIApiKey: process.env.OPENAI_API_KEY, + }); +} + +async function search({ topic }: Pick): Promise { + const retriever = new TavilySearchAPIRetriever({ + k: 10, + apiKey: process.env.TAVILY_API_KEY, + }); + // let topic = state.agentState.topic; + // must be at least 5 characters long + if (topic.length < 5) { + topic = 'topic: ' + topic; + } + const docs = await retriever.invoke(topic); + return JSON.stringify(docs); +} + +async function curate( + input: Pick +): Promise { + const response = await model().invoke( + [ + new SystemMessage( + `You are a personal newspaper editor. + Your sole task is to return a list of URLs of the 5 most relevant articles for the provided topic or query as a JSON list of strings + in this format: + { + urls: ["url1", "url2", "url3", "url4", "url5"] + } + .`.replace(/\s+/g, ' ') + ), + new HumanMessage( + `Today's date is ${new Date().toLocaleDateString('en-GB')}. + Topic or Query: ${input.topic} + + Here is a list of articles: + ${input.searchResults}`.replace(/\s+/g, ' ') + ), + ], + { + response_format: { + type: 'json_object', + }, + } + ); + const urls = JSON.parse(response.content as string).urls; + const searchResults = JSON.parse(input.searchResults!); + const newSearchResults = searchResults.filter((result: any) => { + return urls.includes(result.metadata.source); + }); + return JSON.stringify(newSearchResults); +} + +async function critique( + input: Pick +): Promise { + let feedbackInstructions = ''; + if (input.critique) { + feedbackInstructions = + `The writer has revised the article based on your previous critique: ${input.critique} + The writer might have left feedback for you encoded between tags. + The feedback is only for you to see and will be removed from the final article. + `.replace(/\s+/g, ' '); + } + const response = await model().invoke([ + new SystemMessage( + `You are a personal newspaper writing critique. Your sole purpose is to provide short feedback on a written + article so the writer will know what to fix. + Today's date is ${new Date().toLocaleDateString('en-GB')} + Your task is to provide a really short feedback on the article only if necessary. + if you think the article is good, please return [DONE]. + you can provide feedback on the revised article or just + return [DONE] if you think the article is good. + Please return a string of your critique or [DONE].`.replace(/\s+/g, ' ') + ), + new HumanMessage( + `${feedbackInstructions} + This is the article: ${input.article}` + ), + ]); + const content = response.content as string; + console.log('critique:', content); + return content.includes('[DONE]') ? undefined : content; +} + +async function write( + input: Pick +): Promise { + const response = await model().invoke([ + new SystemMessage( + `You are a personal newspaper writer. Your sole purpose is to write a well-written article about a + topic using a list of articles. Write 5 paragraphs in markdown.`.replace( + /\s+/g, + ' ' + ) + ), + new HumanMessage( + `Today's date is ${new Date().toLocaleDateString('en-GB')}. + Your task is to write a critically acclaimed article for me about the provided query or + topic based on the sources. + Here is a list of articles: ${input.searchResults} + This is the topic: ${input.topic} + Please return a well-written article based on the provided information.`.replace( + /\s+/g, + ' ' + ) + ), + ]); + const content = response.content as string; + return content; +} + +async function revise( + input: Pick +): Promise { + const response = await model().invoke([ + new SystemMessage( + `You are a personal newspaper editor. Your sole purpose is to edit a well-written article about a + topic based on given critique.`.replace(/\s+/g, ' ') + ), + new HumanMessage( + `Your task is to edit the article based on the critique given. + This is the article: ${input.article} + This is the critique: ${input.critique} + Please return the edited article based on the critique given. + You may leave feedback about the critique encoded between tags like this: + here goes the feedback ...`.replace(/\s+/g, ' ') + ), + ]); + const content = response.content as string; + return content; +} + +const machine = setup({ + types: { + context: {} as AgentState, + }, + actors: { + search: fromPromise(({ input }: { input: Pick }) => { + return search(input); + }), + curate: fromPromise( + ({ input }: { input: Pick }) => { + return curate(input); + } + ), + critique: fromPromise( + ({ input }: { input: Pick }) => { + return critique(input); + } + ), + write: fromPromise( + ({ input }: { input: Pick }) => { + return write(input); + } + ), + revise: fromPromise( + ({ input }: { input: Pick }) => { + return revise(input); + } + ), + }, +}).createMachine({ + context: { + topic: 'Orlando', + revisionCount: 0, + }, + initial: 'search', + states: { + search: { + invoke: { + src: 'search', + input: ({ context }) => ({ + topic: context.topic, + }), + onDone: { + actions: assign({ + searchResults: ({ event }) => event.output, + }), + target: 'curate', + }, + }, + }, + curate: { + invoke: { + src: 'curate', + input: ({ context }) => ({ + topic: context.topic, + searchResults: context.searchResults!, + }), + onDone: { + actions: assign({ + searchResults: ({ event }) => event.output, + }), + target: 'write', + }, + }, + }, + write: { + invoke: { + src: 'write', + input: ({ context }) => ({ + topic: context.topic, + searchResults: context.searchResults!, + }), + onDone: { + actions: assign({ + article: ({ event }) => event.output, + }), + target: 'critique', + }, + }, + }, + critique: { + invoke: { + src: 'critique', + input: ({ context }) => ({ + article: context.article!, + critique: context.critique, + }), + onDone: [ + { + guard: ({ event }) => event.output === undefined, + target: 'done', + }, + { + actions: assign({ + article: ({ event }) => event.output, + }), + target: 'revise', + }, + ], + }, + }, + revise: { + always: { + guard: ({ context }) => context.revisionCount > 3, + target: 'done', + }, + entry: assign({ + revisionCount: ({ context }) => context.revisionCount + 1, + }), + invoke: { + src: 'revise', + input: ({ context }) => ({ + article: context.article!, + critique: context.critique, + }), + onDone: { + actions: assign({ + article: ({ event }) => event.output, + }), + target: 'revise', + reenter: true, + }, + }, + }, + done: { + type: 'final', + }, + }, + output: ({ context }) => context.article, +}); + +const actor = createActor(machine, { + // inspect: (inspEv) => { + // if (inspEv.type === '@xstate.event') { + // console.log(JSON.stringify(inspEv.event, null, 2)); + // } + // }, +}); + +actor.subscribe({ + next: (s) => { + console.log('State:', s.value); + console.log( + 'Context:', + JSON.stringify( + s.context, + (k, v) => { + if (typeof v === 'string') { + // truncate if longer than 50 chars + return v.length > 50 ? `${v.slice(0, 50)}...` : v; + } + return v; + }, + 2 + ) + ); + }, + complete: () => { + console.log(actor.getSnapshot().output); + }, + error: (err) => { + console.error(err); + }, +}); + +actor.start(); + +// keep the process alive by invoking a promise that never resolves +new Promise(() => {}); diff --git a/examples/number.ts b/examples_old/number.ts similarity index 100% rename from examples/number.ts rename to examples_old/number.ts diff --git a/examples_old/raffle.ts b/examples_old/raffle.ts new file mode 100644 index 0000000..323672f --- /dev/null +++ b/examples_old/raffle.ts @@ -0,0 +1,104 @@ +import { z } from 'zod'; +import { createAgent, fromDecision } from '../src'; +import { openai } from '@ai-sdk/openai'; +import { assign, createActor, log, setup } from 'xstate'; +import { fromTerminal } from './helpers/helpers'; + +const agent = createAgent({ + name: 'raffle-chooser', + model: openai('gpt-4-turbo'), + events: { + 'agent.collectEntries': z.object({}).describe('Collect more entries'), + 'agent.draw': z.object({}).describe('Draw a winner'), + 'agent.reportWinner': z.object({ + winningEntry: z.string().describe('The winning entry'), + firstRunnerUp: z.string().describe('The first runner up entry'), + secondRunnerUp: z.string().describe('The second runner up entry'), + explanation: z + .string() + .describe('Explanation for why you chose the winning entry'), + }), + }, +}); + +const machine = setup({ + types: { + context: {} as { + lastInput: string | null; + entries: string[]; + }, + events: {} as typeof agent.types.events | { type: 'draw' }, + }, + actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, +}).createMachine({ + context: { + lastInput: null, + entries: [], + }, + initial: 'entering', + states: { + entering: { + entry: log(({ context }) => context.entries), + invoke: { + src: 'getFromTerminal', + input: 'What technology are you most interested in right now?', + onDone: [ + { + actions: assign({ + lastInput: ({ event }) => event.output, + }), + target: 'determining', + }, + ], + }, + }, + determining: { + invoke: { + src: 'agent', + input: { + context: true, + goal: 'If the last input explicitly says to end the drawing and/or choose a winner, start the drawing process. Otherwise, get more entries.', + }, + }, + on: { + 'agent.collectEntries': { + target: 'entering', + actions: assign({ + entries: ({ context }) => [...context.entries, context.lastInput!], + lastInput: null, + }), + }, + 'agent.draw': 'drawing', + }, + }, + drawing: { + entry: log('And the winner is...'), + invoke: { + src: 'agent', + input: { + context: true, + goal: 'Choose the technology that sounds most exciting to you from the entries. Be as unbiased as possible in your choice. Explain why you chose the winning entry.', + }, + }, + on: { + 'agent.reportWinner': { + actions: log( + ({ event }) => + `\n🎉🎉🎉 ${event.winningEntry} 🎉🎉🎉\n\n${event.explanation}` + ), + target: 'winner', + }, + }, + }, + winner: { + type: 'final', + }, + }, + exit: () => { + process.exit(0); + }, +}); + +const actor = createActor(machine); + +actor.start(); diff --git a/examples/sandbox.ts b/examples_old/sandbox.ts similarity index 100% rename from examples/sandbox.ts rename to examples_old/sandbox.ts diff --git a/examples_old/simple.ts b/examples_old/simple.ts new file mode 100644 index 0000000..25c8bab --- /dev/null +++ b/examples_old/simple.ts @@ -0,0 +1,39 @@ +import { createAgent, fromDecision } from '../src'; +import { z } from 'zod'; +import { setup, createActor } from 'xstate'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + name: 'simple', + model: openai('gpt-3.5-turbo-16k-0613'), + events: { + 'agent.thought': z.object({ + text: z.string().describe('The text of the thought'), + }), + }, +}); + +const machine = setup({ + actors: { agent: fromDecision(agent) }, +}).createMachine({ + initial: 'thinking', + states: { + thinking: { + invoke: { + src: 'agent', + input: 'Think about a random topic, and then share that thought.', + }, + on: { + 'agent.thought': { + actions: ({ event }) => console.log(event.text), + target: 'thought', + }, + }, + }, + thought: { + type: 'final', + }, + }, +}); + +const actor = createActor(machine).start(); diff --git a/examples/support.ts b/examples_old/support.ts similarity index 100% rename from examples/support.ts rename to examples_old/support.ts diff --git a/examples/ticTacToe.ts b/examples_old/ticTacToe.ts similarity index 100% rename from examples/ticTacToe.ts rename to examples_old/ticTacToe.ts diff --git a/examples/todo.ts b/examples_old/todo.ts similarity index 100% rename from examples/todo.ts rename to examples_old/todo.ts diff --git a/examples_old/tutor.ts b/examples_old/tutor.ts new file mode 100644 index 0000000..3a11f6b --- /dev/null +++ b/examples_old/tutor.ts @@ -0,0 +1,100 @@ +import { assign, createActor, log, setup } from 'xstate'; +import { fromTerminal } from './helpers/helpers'; +import { createAgent, fromDecision } from '../src'; +import { z } from 'zod'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + name: 'tutor', + model: openai('gpt-4-1106-preview'), + events: { + teach: z.object({ + instruction: z + .string() + .describe( + 'The feedback to give the human, correcting any grammatical errors, misspellings, etc.' + ), + }), + respond: z.object({ + response: z.string().describe('The response to the human in Spanish'), + }), + }, + system: + 'You are an expert Spanish tutor. You will respond to the human in Spanish.', +}); + +const machine = setup({ + types: { + context: {} as { + conversation: string[]; + }, + events: agent.types.events, + }, + actors: { agent: fromDecision(agent), getFromTerminal: fromTerminal }, +}).createMachine({ + initial: 'human', + context: { + conversation: [], + }, + states: { + human: { + invoke: { + src: 'getFromTerminal', + input: 'Say something in Spanish:', + onDone: { + actions: assign({ + conversation: ({ context, event }) => + context.conversation.concat(`User: ` + event.output), + }), + target: 'ai', + }, + }, + }, + ai: { + initial: 'teaching', + states: { + teaching: { + invoke: { + src: 'agent', + input: () => ({ + context: true, + goal: 'Give brief feedback to the human based on the most recent response of the conversation', + maxTokens: 100, + }), + }, + on: { + teach: { + actions: ({ event }) => console.log(event.instruction), + target: 'responding', + }, + }, + }, + responding: { + invoke: { + src: 'agent', + input: () => ({ + context: true, + goal: 'Respond to the last message of the conversation in Spanish', + }), + }, + on: { + respond: { + actions: [ + assign({ + conversation: ({ context, event }) => + context.conversation.concat(`Agent: ` + event.response), + }), + log(({ event }) => event.response), + ], + target: 'done', + }, + }, + }, + done: { type: 'final' }, + }, + onDone: { target: 'human' }, + }, + }, +}); + +createActor(machine).start(); diff --git a/examples/verify.ts b/examples_old/verify.ts similarity index 100% rename from examples/verify.ts rename to examples_old/verify.ts diff --git a/examples/weather.ts b/examples_old/weather.ts similarity index 100% rename from examples/weather.ts rename to examples_old/weather.ts diff --git a/examples/wiki.ts b/examples_old/wiki.ts similarity index 100% rename from examples/wiki.ts rename to examples_old/wiki.ts diff --git a/examples/word.ts b/examples_old/word.ts similarity index 100% rename from examples/word.ts rename to examples_old/word.ts diff --git a/package.json b/package.json index 019cbb9..4457cd0 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,103 @@ { "name": "@statelyai/agent", - "version": "1.1.6", - "description": "Stateful agents that make decisions based on finite-state machine models", - "main": "dist/index.js", + "version": "2.0.0", + "description": "Lightweight, stateless, framework-agnostic state-machine-driven AI agents", + "type": "module", + "main": "dist/index.cjs", "module": "dist/index.mjs", - "types": "dist/index.d.ts", + "types": "dist/index.d.mts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./ai-sdk": { + "import": { + "types": "./dist/ai-sdk.d.mts", + "default": "./dist/ai-sdk.mjs" + }, + "require": { + "types": "./dist/ai-sdk.d.cts", + "default": "./dist/ai-sdk.cjs" + } + }, + "./cloudflare": { + "import": { + "types": "./dist/cloudflare.d.mts", + "default": "./dist/cloudflare.mjs" + }, + "require": { + "types": "./dist/cloudflare.d.cts", + "default": "./dist/cloudflare.cjs" + } + }, + "./graph": { + "import": { + "types": "./dist/graph.d.mts", + "default": "./dist/graph.mjs" + }, + "require": { + "types": "./dist/graph.d.cts", + "default": "./dist/graph.cjs" + } + }, + "./http": { + "import": { + "types": "./dist/http.d.mts", + "default": "./dist/http.mjs" + }, + "require": { + "types": "./dist/http.d.cts", + "default": "./dist/http.cjs" + } + }, + "./next": { + "import": { + "types": "./dist/next.d.mts", + "default": "./dist/next.mjs" + }, + "require": { + "types": "./dist/next.d.cts", + "default": "./dist/next.cjs" + } + }, + "./runtime": { + "import": { + "types": "./dist/runtime.d.mts", + "default": "./dist/runtime.mjs" + }, + "require": { + "types": "./dist/runtime.d.cts", + "default": "./dist/runtime.cjs" + } + }, + "./xstate": { + "import": { + "types": "./dist/xstate.d.mts", + "default": "./dist/xstate.mjs" + }, + "require": { + "types": "./dist/xstate.d.cts", + "default": "./dist/xstate.cjs" + } + } + }, + "files": [ + "dist" + ], "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts", + "agent:convert": "tsx scripts/agent-convert.ts", + "build": "tsdown", "lint": "tsc --noEmit", "test": "vitest", "test:ci": "vitest --run", - "example": "ts-node examples/helpers/runner.ts", - "prepublishOnly": "tsup src/index.ts --format cjs,esm --dts", + "prepublishOnly": "tsdown", "changeset": "changeset", "release": "changeset publish", "version": "changeset version" @@ -20,37 +106,43 @@ "ai", "state machine", "agent", - "rl", - "reinforcement learning" + "statechart", + "classify", + "decide" ], "author": "David Khourshid ", "license": "MIT", + "engines": { + "node": ">=22.18.0" + }, "devDependencies": { - "@ai-sdk/openai": "^0.0.40", + "@ai-sdk/openai": "^3.0.25", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.9", - "@langchain/community": "^0.0.53", - "@langchain/core": "^0.1.63", - "@langchain/openai": "^0.0.28", "@types/node": "^20.16.10", - "@types/object-hash": "^3.0.6", + "agents": "0.11.5", "dotenv": "^16.4.5", - "json-schema-to-ts": "^3.1.1", - "ts-node": "^10.9.2", - "tsup": "^8.3.0", - "typescript": "^5.6.2", + "tsdown": "^0.21.7", + "tsx": "^4.21.0", "vitest": "^2.1.2", - "wikipedia": "^2.1.2", - "zod": "^3.23.8" + "zod": "^4.3.6" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } }, "publishConfig": { "access": "public" }, "dependencies": { - "@xstate/graph": "^2.0.1", - "ai": "^3.4.9", - "object-hash": "^3.0.0", - "xstate": "^5.18.2" + "@statelyai/graph": "^0.11.0", + "ai": "^6.0.67", + "typescript": "^5.6.2", + "xstate": "^5.26.0" }, - "packageManager": "pnpm@8.11.0" + "packageManager": "pnpm@10.28.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d617c3e..4053207 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,239 +8,289 @@ importers: .: dependencies: - '@xstate/graph': - specifier: ^2.0.1 - version: 2.0.1(xstate@5.18.2) + '@statelyai/graph': + specifier: ^0.11.0 + version: 0.11.0(zod@4.3.6) ai: - specifier: ^3.4.9 - version: 3.4.9(openai@4.67.1(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.6.2))(zod@3.23.8) - object-hash: - specifier: ^3.0.0 - version: 3.0.0 + specifier: ^6.0.67 + version: 6.0.67(zod@4.3.6) + typescript: + specifier: ^5.6.2 + version: 5.9.3 xstate: - specifier: ^5.18.2 - version: 5.18.2 + specifier: ^5.26.0 + version: 5.26.0 devDependencies: '@ai-sdk/openai': - specifier: ^0.0.40 - version: 0.0.40(zod@3.23.8) + specifier: ^3.0.25 + version: 3.0.25(zod@4.3.6) '@changesets/changelog-github': specifier: ^0.5.0 - version: 0.5.0 + version: 0.5.2 '@changesets/cli': specifier: ^2.27.9 - version: 2.27.9 - '@langchain/community': - specifier: ^0.0.53 - version: 0.0.53(openai@4.67.1(zod@3.23.8)) - '@langchain/core': - specifier: ^0.1.63 - version: 0.1.63(openai@4.67.1(zod@3.23.8)) - '@langchain/openai': - specifier: ^0.0.28 - version: 0.0.28 + version: 2.29.8(@types/node@20.19.30) '@types/node': specifier: ^20.16.10 - version: 20.16.10 - '@types/object-hash': - specifier: ^3.0.6 - version: 3.0.6 + version: 20.19.30 + agents: + specifier: 0.11.5 + version: 0.11.5(@babel/core@7.29.0)(@babel/runtime@7.28.6)(@cloudflare/workers-types@4.20260424.1)(ai@6.0.67(zod@4.3.6))(react@19.2.5)(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(vite@5.4.21(@types/node@20.19.30))(zod@4.3.6) dotenv: specifier: ^16.4.5 - version: 16.4.5 - json-schema-to-ts: - specifier: ^3.1.1 - version: 3.1.1 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) - tsup: - specifier: ^8.3.0 - version: 8.3.0(postcss@8.4.47)(typescript@5.6.2) - typescript: - specifier: ^5.6.2 - version: 5.6.2 + version: 16.6.1 + tsdown: + specifier: ^0.21.7 + version: 0.21.7(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(typescript@5.9.3) + tsx: + specifier: ^4.21.0 + version: 4.21.0 vitest: specifier: ^2.1.2 - version: 2.1.2(@types/node@20.16.10) - wikipedia: - specifier: ^2.1.2 - version: 2.1.2 + version: 2.1.9(@types/node@20.19.30) zod: - specifier: ^3.23.8 - version: 3.23.8 + specifier: ^4.3.6 + version: 4.3.6 packages: - '@ai-sdk/openai@0.0.40': - resolution: {integrity: sha512-9Iq1UaBHA5ZzNv6j3govuKGXrbrjuWvZIgWNJv4xzXlDMHu9P9hnqlBr/Aiay54WwCuTVNhTzAUTfFgnTs2kbQ==, tarball: https://registry.npmjs.org/@ai-sdk/openai/-/openai-0.0.40.tgz} + '@ai-sdk/gateway@3.0.32': + resolution: {integrity: sha512-7clZRr07P9rpur39t1RrbIe7x8jmwnwUWI8tZs+BvAfX3NFgdSVGGIaT7bTz2pb08jmLXzTSDbrOTqAQ7uBkBQ==, tarball: https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.32.tgz} engines: {node: '>=18'} peerDependencies: - zod: ^3.0.0 + zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@1.0.20': - resolution: {integrity: sha512-ngg/RGpnA00eNOWEtXHenpX1MsM2QshQh4QJFjUfwcqHpM5kTfG7je7Rc3HcEDP+OkRVv2GF+X4fC1Vfcnl8Ow==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.20.tgz} + '@ai-sdk/openai@3.0.25': + resolution: {integrity: sha512-DsaN46R98+D1W3lU3fKuPU3ofacboLaHlkAwxJPgJ8eup1AJHmPK1N1y10eJJbJcF6iby8Tf/vanoZxc9JPUfw==, tarball: https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.25.tgz} engines: {node: '>=18'} peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true + zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@1.0.5': - resolution: {integrity: sha512-XfOawxk95X3S43arn2iQIFyWGMi0DTxsf9ETc6t7bh91RPWOOPYN1tsmS5MTKD33OGJeaDQ/gnVRzXUCRBrckQ==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.5.tgz} + '@ai-sdk/provider-utils@4.0.13': + resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.13.tgz} engines: {node: '>=18'} peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true + zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@0.0.14': - resolution: {integrity: sha512-gaQ5Y033nro9iX1YUjEDFDRhmMcEiCk56LJdIUbX5ozEiCNCfpiBpEqrjSp/Gp5RzBS2W0BVxfG7UGW6Ezcrzg==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.14.tgz} + '@ai-sdk/provider@3.0.7': + resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.7.tgz} engines: {node: '>=18'} - '@ai-sdk/provider@0.0.24': - resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.24.tgz} - engines: {node: '>=18'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz} + engines: {node: '>=6.9.0'} - '@ai-sdk/react@0.0.62': - resolution: {integrity: sha512-1asDpxgmeHWL0/EZPCLENxfOHT+0jce0z/zasRhascodm2S6f6/KZn5doLG9jdmarcb+GjMjFmmwyOVXz3W1xg==, tarball: https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.62.tgz} - engines: {node: '>=18'} - peerDependencies: - react: ^18 || ^19 - zod: ^3.0.0 - peerDependenciesMeta: - react: - optional: true - zod: - optional: true + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz} + engines: {node: '>=6.9.0'} - '@ai-sdk/solid@0.0.49': - resolution: {integrity: sha512-KnfWTt640cS1hM2fFIba8KHSPLpOIWXtEm28pNCHTvqasVKlh2y/zMQANTwE18pF2nuXL9P9F5/dKWaPsaEzQw==, tarball: https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.49.tgz} - engines: {node: '>=18'} - peerDependencies: - solid-js: ^1.7.7 - peerDependenciesMeta: - solid-js: - optional: true + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz} + engines: {node: '>=6.9.0'} - '@ai-sdk/svelte@0.0.51': - resolution: {integrity: sha512-aIZJaIds+KpCt19yUDCRDWebzF/17GCY7gN9KkcA2QM6IKRO5UmMcqEYja0ZmwFQPm1kBZkF2njhr8VXis2mAw==, tarball: https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.51.tgz} - engines: {node: '>=18'} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/generator@8.0.0-rc.3': + resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.3.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==, tarball: https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, tarball: https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==, tarball: https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz} + engines: {node: '>=6.9.0'} peerDependencies: - svelte: ^3.0.0 || ^4.0.0 - peerDependenciesMeta: - svelte: - optional: true + '@babel/core': ^7.0.0 - '@ai-sdk/ui-utils@0.0.46': - resolution: {integrity: sha512-ZG/wneyJG+6w5Nm/hy1AKMuRgjPQToAxBsTk61c9sVPUTaxo+NNjM2MhXQMtmsja2N5evs8NmHie+ExEgpL3cA==, tarball: https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.46.tgz} - engines: {node: '>=18'} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, tarball: https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==, tarball: https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, tarball: https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, tarball: https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz} + engines: {node: '>=6.9.0'} peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true + '@babel/core': ^7.0.0 - '@ai-sdk/vue@0.0.54': - resolution: {integrity: sha512-Ltu6gbuii8Qlp3gg7zdwdnHdS4M8nqKDij2VVO1223VOtIFwORFJzKqpfx44U11FW8z2TPVBYN+FjkyVIcN2hg==, tarball: https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.54.tgz} - engines: {node: '>=18'} + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==, tarball: https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, tarball: https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==, tarball: https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz} + engines: {node: '>=6.9.0'} peerDependencies: - vue: ^3.3.4 - peerDependenciesMeta: - vue: - optional: true + '@babel/core': ^7.0.0 - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, tarball: https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz} - engines: {node: '>=6.0.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==, tarball: https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz} + engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.7': - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==, tarball: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, tarball: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz} + '@babel/helper-string-parser@8.0.0-rc.3': + resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==, tarball: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.3.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz} engines: {node: '>=6.9.0'} - '@babel/parser@7.25.7': - resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz} + '@babel/helper-validator-identifier@8.0.0-rc.3': + resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.3.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, tarball: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==, tarball: https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.25.7': - resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==, tarball: https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz} + '@babel/parser@8.0.0-rc.3': + resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.3.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + '@babel/plugin-proposal-decorators@7.29.0': + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==, tarball: https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@babel/types@7.25.7': - resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz} + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==, tarball: https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@changesets/apply-release-plan@7.0.5': - resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==, tarball: https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.5.tgz} + '@babel/runtime-corejs3@7.29.2': + resolution: {integrity: sha512-Lc94FOD5+0aXhdb0Tdg3RUtqT6yWbI/BbFWvlaSJ3gAb9Ks+99nHRDKADVqC37er4eCB0fHyWT+y+K3QOvJKbw==, tarball: https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.2.tgz} + engines: {node: '>=6.9.0'} - '@changesets/assemble-release-plan@6.0.4': - resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==, tarball: https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.4.tgz} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, tarball: https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz} + engines: {node: '>=6.9.0'} - '@changesets/changelog-git@0.2.0': - resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==, tarball: https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.0.tgz} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, tarball: https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz} + engines: {node: '>=6.9.0'} - '@changesets/changelog-github@0.5.0': - resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==, tarball: https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.5.0.tgz} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz} + engines: {node: '>=6.9.0'} - '@changesets/cli@2.27.9': - resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==, tarball: https://registry.npmjs.org/@changesets/cli/-/cli-2.27.9.tgz} + '@babel/types@8.0.0-rc.3': + resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==, tarball: https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.3.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==, tarball: https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz} + + '@changesets/apply-release-plan@7.0.14': + resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==, tarball: https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.14.tgz} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==, tarball: https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==, tarball: https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz} + + '@changesets/changelog-github@0.5.2': + resolution: {integrity: sha512-HeGeDl8HaIGj9fQHo/tv5XKQ2SNEi9+9yl1Bss1jttPqeiASRXhfi0A2wv8yFKCp07kR1gpOI5ge6+CWNm1jPw==, tarball: https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.5.2.tgz} + + '@changesets/cli@2.29.8': + resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==, tarball: https://registry.npmjs.org/@changesets/cli/-/cli-2.29.8.tgz} hasBin: true - '@changesets/config@3.0.3': - resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==, tarball: https://registry.npmjs.org/@changesets/config/-/config-3.0.3.tgz} + '@changesets/config@3.1.2': + resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==, tarball: https://registry.npmjs.org/@changesets/config/-/config-3.1.2.tgz} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==, tarball: https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz} - '@changesets/get-dependents-graph@2.1.2': - resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==, tarball: https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.2.tgz} + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==, tarball: https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz} - '@changesets/get-github-info@0.6.0': - resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==, tarball: https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.6.0.tgz} + '@changesets/get-github-info@0.7.0': + resolution: {integrity: sha512-+i67Bmhfj9V4KfDeS1+Tz3iF32btKZB2AAx+cYMqDSRFP7r3/ZdGbjCo+c6qkyViN9ygDuBjzageuPGJtKGe5A==, tarball: https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.7.0.tgz} - '@changesets/get-release-plan@4.0.4': - resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==, tarball: https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.4.tgz} + '@changesets/get-release-plan@4.0.14': + resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==, tarball: https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.14.tgz} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==, tarball: https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz} - '@changesets/git@3.0.1': - resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==, tarball: https://registry.npmjs.org/@changesets/git/-/git-3.0.1.tgz} + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==, tarball: https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz} '@changesets/logger@0.1.1': resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==, tarball: https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz} - '@changesets/parse@0.4.0': - resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==, tarball: https://registry.npmjs.org/@changesets/parse/-/parse-0.4.0.tgz} + '@changesets/parse@0.4.2': + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==, tarball: https://registry.npmjs.org/@changesets/parse/-/parse-0.4.2.tgz} - '@changesets/pre@2.0.1': - resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==, tarball: https://registry.npmjs.org/@changesets/pre/-/pre-2.0.1.tgz} + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==, tarball: https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz} - '@changesets/read@0.6.1': - resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==, tarball: https://registry.npmjs.org/@changesets/read/-/read-0.6.1.tgz} + '@changesets/read@0.6.6': + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==, tarball: https://registry.npmjs.org/@changesets/read/-/read-0.6.6.tgz} - '@changesets/should-skip-package@0.1.1': - resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==, tarball: https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.1.tgz} + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==, tarball: https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==, tarball: https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz} - '@changesets/types@6.0.0': - resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==, tarball: https://registry.npmjs.org/@changesets/types/-/types-6.0.0.tgz} + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==, tarball: https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz} - '@changesets/write@0.3.2': - resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==, tarball: https://registry.npmjs.org/@changesets/write/-/write-0.3.2.tgz} + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==, tarball: https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, tarball: https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz} - engines: {node: '>=12'} + '@cloudflare/workers-types@4.20260424.1': + resolution: {integrity: sha512-0DLJ9yEk1KKzPbqop80Gw/P1wkKKzawmipULiJWdBXIBCoMvE0OVWms3IrL/Q/G7tfmPop9yF4XlZ69k9JLYng==, tarball: https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260424.1.tgz} + + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==, tarball: https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==, tarball: https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==, tarball: https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz} '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} @@ -248,8 +298,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -260,8 +310,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -272,8 +322,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -284,8 +334,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -296,8 +346,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -308,8 +358,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -320,8 +370,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -332,8 +382,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -344,8 +394,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -356,8 +406,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -368,8 +418,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -380,8 +430,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -392,8 +442,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -404,8 +454,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -416,8 +466,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -428,8 +478,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -440,26 +490,32 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -470,20 +526,26 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -494,8 +556,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -506,8 +568,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -518,332 +580,64 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, tarball: https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz} - engines: {node: '>=12'} + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==, tarball: https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==, tarball: https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz} - engines: {node: '>=6.0.0'} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==, tarball: https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, tarball: https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, tarball: https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, tarball: https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, tarball: https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, tarball: https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, tarball: https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==, tarball: https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, tarball: https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz} + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==, tarball: https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz} - '@langchain/community@0.0.53': - resolution: {integrity: sha512-iFqZPt4MRssGYsQoKSXWJQaYTZCC7WNuilp2JCCs3wKmJK3l6mR0eV+PDrnT+TaDHUVxt/b0rwgM0sOiy0j2jA==, tarball: https://registry.npmjs.org/@langchain/community/-/community-0.0.53.tgz} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==, tarball: https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz} engines: {node: '>=18'} peerDependencies: - '@aws-crypto/sha256-js': ^5.0.0 - '@aws-sdk/client-bedrock-agent-runtime': ^3.485.0 - '@aws-sdk/client-bedrock-runtime': ^3.422.0 - '@aws-sdk/client-dynamodb': ^3.310.0 - '@aws-sdk/client-kendra': ^3.352.0 - '@aws-sdk/client-lambda': ^3.310.0 - '@aws-sdk/client-sagemaker-runtime': ^3.310.0 - '@aws-sdk/client-sfn': ^3.310.0 - '@aws-sdk/credential-provider-node': ^3.388.0 - '@azure/search-documents': ^12.0.0 - '@clickhouse/client': ^0.2.5 - '@cloudflare/ai': '*' - '@datastax/astra-db-ts': ^1.0.0 - '@elastic/elasticsearch': ^8.4.0 - '@getmetal/metal-sdk': '*' - '@getzep/zep-js': ^0.9.0 - '@gomomento/sdk': ^1.51.1 - '@gomomento/sdk-core': ^1.51.1 - '@google-ai/generativelanguage': ^0.2.1 - '@gradientai/nodejs-sdk': ^1.2.0 - '@huggingface/inference': ^2.6.4 - '@mozilla/readability': '*' - '@neondatabase/serverless': '*' - '@opensearch-project/opensearch': '*' - '@pinecone-database/pinecone': '*' - '@planetscale/database': ^1.8.0 - '@premai/prem-sdk': ^0.3.25 - '@qdrant/js-client-rest': ^1.8.2 - '@raycast/api': ^1.55.2 - '@rockset/client': ^0.9.1 - '@smithy/eventstream-codec': ^2.0.5 - '@smithy/protocol-http': ^3.0.6 - '@smithy/signature-v4': ^2.0.10 - '@smithy/util-utf8': ^2.0.0 - '@supabase/postgrest-js': ^1.1.1 - '@supabase/supabase-js': ^2.10.0 - '@tensorflow-models/universal-sentence-encoder': '*' - '@tensorflow/tfjs-converter': '*' - '@tensorflow/tfjs-core': '*' - '@upstash/redis': ^1.20.6 - '@upstash/vector': ^1.0.7 - '@vercel/kv': ^0.2.3 - '@vercel/postgres': ^0.5.0 - '@writerai/writer-sdk': ^0.40.2 - '@xata.io/client': ^0.28.0 - '@xenova/transformers': ^2.5.4 - '@zilliz/milvus2-sdk-node': '>=2.2.7' - better-sqlite3: ^9.4.0 - cassandra-driver: ^4.7.2 - cborg: ^4.1.1 - chromadb: '*' - closevector-common: 0.1.3 - closevector-node: 0.1.6 - closevector-web: 0.1.6 - cohere-ai: '*' - convex: ^1.3.1 - couchbase: ^4.3.0 - discord.js: ^14.14.1 - dria: ^0.0.3 - duck-duck-scrape: ^2.2.5 - faiss-node: ^0.5.1 - firebase-admin: ^11.9.0 || ^12.0.0 - google-auth-library: ^8.9.0 - googleapis: ^126.0.1 - hnswlib-node: ^3.0.0 - html-to-text: ^9.0.5 - interface-datastore: ^8.2.11 - ioredis: ^5.3.2 - it-all: ^3.0.4 - jsdom: '*' - jsonwebtoken: ^9.0.2 - llmonitor: ^0.5.9 - lodash: ^4.17.21 - lunary: ^0.6.11 - mongodb: '>=5.2.0' - mysql2: ^3.3.3 - neo4j-driver: '*' - node-llama-cpp: '*' - pg: ^8.11.0 - pg-copy-streams: ^6.0.5 - pickleparser: ^0.2.1 - portkey-ai: ^0.1.11 - redis: '*' - replicate: ^0.18.0 - typeorm: ^0.3.12 - typesense: ^1.5.3 - usearch: ^1.1.1 - vectordb: ^0.1.4 - voy-search: 0.6.2 - weaviate-ts-client: '*' - web-auth-library: ^1.0.3 - ws: ^8.14.2 + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 peerDependenciesMeta: - '@aws-crypto/sha256-js': - optional: true - '@aws-sdk/client-bedrock-agent-runtime': - optional: true - '@aws-sdk/client-bedrock-runtime': - optional: true - '@aws-sdk/client-dynamodb': - optional: true - '@aws-sdk/client-kendra': - optional: true - '@aws-sdk/client-lambda': - optional: true - '@aws-sdk/client-sagemaker-runtime': - optional: true - '@aws-sdk/client-sfn': - optional: true - '@aws-sdk/credential-provider-node': - optional: true - '@azure/search-documents': - optional: true - '@clickhouse/client': - optional: true - '@cloudflare/ai': - optional: true - '@datastax/astra-db-ts': - optional: true - '@elastic/elasticsearch': - optional: true - '@getmetal/metal-sdk': - optional: true - '@getzep/zep-js': - optional: true - '@gomomento/sdk': - optional: true - '@gomomento/sdk-core': - optional: true - '@google-ai/generativelanguage': - optional: true - '@gradientai/nodejs-sdk': - optional: true - '@huggingface/inference': - optional: true - '@mozilla/readability': - optional: true - '@neondatabase/serverless': - optional: true - '@opensearch-project/opensearch': - optional: true - '@pinecone-database/pinecone': - optional: true - '@planetscale/database': - optional: true - '@premai/prem-sdk': - optional: true - '@qdrant/js-client-rest': - optional: true - '@raycast/api': - optional: true - '@rockset/client': - optional: true - '@smithy/eventstream-codec': - optional: true - '@smithy/protocol-http': - optional: true - '@smithy/signature-v4': - optional: true - '@smithy/util-utf8': - optional: true - '@supabase/postgrest-js': - optional: true - '@supabase/supabase-js': - optional: true - '@tensorflow-models/universal-sentence-encoder': - optional: true - '@tensorflow/tfjs-converter': - optional: true - '@tensorflow/tfjs-core': - optional: true - '@upstash/redis': - optional: true - '@upstash/vector': - optional: true - '@vercel/kv': - optional: true - '@vercel/postgres': - optional: true - '@writerai/writer-sdk': - optional: true - '@xata.io/client': - optional: true - '@xenova/transformers': - optional: true - '@zilliz/milvus2-sdk-node': - optional: true - better-sqlite3: - optional: true - cassandra-driver: - optional: true - cborg: - optional: true - chromadb: - optional: true - closevector-common: - optional: true - closevector-node: - optional: true - closevector-web: - optional: true - cohere-ai: - optional: true - convex: - optional: true - couchbase: - optional: true - discord.js: - optional: true - dria: - optional: true - duck-duck-scrape: - optional: true - faiss-node: - optional: true - firebase-admin: - optional: true - google-auth-library: - optional: true - googleapis: - optional: true - hnswlib-node: - optional: true - html-to-text: - optional: true - interface-datastore: - optional: true - ioredis: - optional: true - it-all: - optional: true - jsdom: - optional: true - jsonwebtoken: - optional: true - llmonitor: - optional: true - lodash: - optional: true - lunary: - optional: true - mongodb: - optional: true - mysql2: - optional: true - neo4j-driver: - optional: true - node-llama-cpp: - optional: true - pg: - optional: true - pg-copy-streams: - optional: true - pickleparser: - optional: true - portkey-ai: - optional: true - redis: - optional: true - replicate: - optional: true - typeorm: - optional: true - typesense: - optional: true - usearch: - optional: true - vectordb: - optional: true - voy-search: - optional: true - weaviate-ts-client: - optional: true - web-auth-library: - optional: true - ws: + '@cfworker/json-schema': optional: true - '@langchain/core@0.1.63': - resolution: {integrity: sha512-+fjyYi8wy6x1P+Ee1RWfIIEyxd9Ee9jksEwvrggPwwI/p45kIDTdYTblXsM13y4mNWTiACyLSdbwnPaxxdoz+w==, tarball: https://registry.npmjs.org/@langchain/core/-/core-0.1.63.tgz} - engines: {node: '>=18'} - - '@langchain/openai@0.0.28': - resolution: {integrity: sha512-2s1RA3/eAnz4ahdzsMPBna9hfAqpFNlWdHiPxVGZ5yrhXsbLWWoPcF+22LCk9t0HJKtazi2GCIWc0HVXH9Abig==, tarball: https://registry.npmjs.org/@langchain/openai/-/openai-0.0.28.tgz} - engines: {node: '>=18'} - - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==, tarball: https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz} - - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==, tarball: https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz} + '@napi-rs/wasm-runtime@1.1.2': + resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==, tarball: https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, tarball: https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz} @@ -861,137 +655,317 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==, tarball: https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz} engines: {node: '>=8.0.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} - engines: {node: '>=14'} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==, tarball: https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==, tarball: https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz} + + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==, tarball: https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==, tarball: https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==, tarball: https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==, tarball: https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==, tarball: https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==, tarball: https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==, tarball: https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==, tarball: https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==, tarball: https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/plugin-babel@0.2.3': + resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==, tarball: https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==, tarball: https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz} - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz} + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz} + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz} + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz} + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz} + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz} + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz} + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz} + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz} + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz} + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz} + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz} + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz} + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz} cpu: [x64] os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==, tarball: https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz} + cpu: [x64] + os: [openbsd] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz} + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==, tarball: https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz} + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz} + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz} cpu: [x64] os: [win32] - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==, tarball: https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==, tarball: https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz} + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz} + cpu: [x64] + os: [win32] - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==, tarball: https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, tarball: https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz} - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==, tarball: https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz} + '@statelyai/graph@0.11.0': + resolution: {integrity: sha512-qm1AmeXTQu6wrA5o4bXIlic4BDWLJCnNisHoqDan7Qa/UWi3yGg10W8xobItql+qvnVcoghJN1ZmR3cUFeHV7A==, tarball: https://registry.npmjs.org/@statelyai/graph/-/graph-0.11.0.tgz} + peerDependencies: + cytoscape: ^3.0.0 + d3-force: ^3.0.0 + dotparser: ^1.0.0 + elkjs: ^0.9.0 || ^0.10.0 || ^0.11.0 + fast-xml-parser: ^5.0.0 + zod: ^4.0.0 + peerDependenciesMeta: + cytoscape: + optional: true + d3-force: + optional: true + dotparser: + optional: true + elkjs: + optional: true + fast-xml-parser: + optional: true + zod: + optional: true - '@types/diff-match-patch@1.0.36': - resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==, tarball: https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, tarball: https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==, tarball: https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, tarball: https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz} - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==, tarball: https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==, tarball: https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz} '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==, tarball: https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz} - '@types/node@18.19.54': - resolution: {integrity: sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==, tarball: https://registry.npmjs.org/@types/node/-/node-18.19.54.tgz} - - '@types/node@20.16.10': - resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==, tarball: https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz} - - '@types/object-hash@3.0.6': - resolution: {integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==, tarball: https://registry.npmjs.org/@types/object-hash/-/object-hash-3.0.6.tgz} - - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==, tarball: https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz} + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==, tarball: https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==, tarball: https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz} + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==, tarball: https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz} + engines: {node: '>= 20'} - '@vitest/expect@2.1.2': - resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz} + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz} - '@vitest/mocker@2.1.2': - resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz} + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz} peerDependencies: - '@vitest/spy': 2.1.2 - msw: ^2.3.5 + msw: ^2.4.9 vite: ^5.0.0 peerDependenciesMeta: msw: @@ -999,93 +973,69 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.2': - resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz} - - '@vitest/runner@2.1.2': - resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz} - - '@vitest/snapshot@2.1.2': - resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==, tarball: https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz} - - '@vitest/spy@2.1.2': - resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz} - - '@vitest/utils@2.1.2': - resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz} + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz} - '@vue/compiler-core@3.5.11': - resolution: {integrity: sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==, tarball: https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.11.tgz} - - '@vue/compiler-dom@3.5.11': - resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==, tarball: https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz} - - '@vue/compiler-sfc@3.5.11': - resolution: {integrity: sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==, tarball: https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz} - - '@vue/compiler-ssr@3.5.11': - resolution: {integrity: sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==, tarball: https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz} - - '@vue/reactivity@3.5.11': - resolution: {integrity: sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==, tarball: https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz} - - '@vue/runtime-core@3.5.11': - resolution: {integrity: sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==, tarball: https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.11.tgz} - - '@vue/runtime-dom@3.5.11': - resolution: {integrity: sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==, tarball: https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz} - - '@vue/server-renderer@3.5.11': - resolution: {integrity: sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==, tarball: https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.11.tgz} - peerDependencies: - vue: 3.5.11 + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz} - '@vue/shared@3.5.11': - resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==, tarball: https://registry.npmjs.org/@vue/shared/-/shared-3.5.11.tgz} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==, tarball: https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz} - '@xstate/graph@2.0.1': - resolution: {integrity: sha512-WWfL97yvyVISbmetqrspd6mUn13UKoHZ+/FBSU17n+YPdMrYnKaP8UDe/HjNoZAVYsR3wuQLoitTW9cxud0DIA==, tarball: https://registry.npmjs.org/@xstate/graph/-/graph-2.0.1.tgz} - peerDependencies: - xstate: ^5.18.2 + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==, tarball: https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz} - engines: {node: '>=6.5'} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz} - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==, tarball: https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz} - engines: {node: '>=0.4.0'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==, tarball: https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz} + engines: {node: '>= 0.6'} - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==, tarball: https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz} - engines: {node: '>=0.4.0'} + agents@0.11.5: + resolution: {integrity: sha512-1wPkA7OOfEdR4GKwaBmqdnZkOxutN2mCsolVU4ekg5QxrTLnC9Vz9LyZPcGqV2ldyfpUY7R73AUqtig5iYRLvQ==, tarball: https://registry.npmjs.org/agents/-/agents-0.11.5.tgz} hasBin: true - - agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==, tarball: https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz} - engines: {node: '>= 8.0.0'} - - ai@3.4.9: - resolution: {integrity: sha512-wmVzpIHNGjCEjIJ/3945a/DIkz+gwObjC767ZRgO8AmtIZMO5KqvqNr7n2KF+gQrCPCMC8fM1ICQFXSvBZnBlA==, tarball: https://registry.npmjs.org/ai/-/ai-3.4.9.tgz} - engines: {node: '>=18'} peerDependencies: - openai: ^4.42.0 - react: ^18 || ^19 - sswr: ^2.1.0 - svelte: ^3.0.0 || ^4.0.0 - zod: ^3.0.0 + '@cloudflare/ai-chat': '>=0.0.8 <1.0.0' + '@cloudflare/codemode': '>=0.0.7 <1.0.0' + '@tanstack/ai': '>=0.10.2 <1.0.0' + '@x402/core': ^2.0.0 + '@x402/evm': ^2.0.0 + ai: ^6.0.0 + react: ^19.0.0 + vite: '>=6.0.0 <9.0.0' + zod: ^4.0.0 peerDependenciesMeta: - openai: + '@cloudflare/ai-chat': optional: true - react: + '@cloudflare/codemode': optional: true - sswr: + '@tanstack/ai': optional: true - svelte: + '@x402/core': optional: true - zod: + '@x402/evm': + optional: true + vite: + optional: true + + ai@6.0.67: + resolution: {integrity: sha512-xBnTcByHCj3OcG6V8G1s6zvSEqK0Bdiu+IEXYcpGrve1iGFFRgcrKeZtr/WAW/7gupnSvBbDF24BEv1OOfqi1g==, tarball: https://registry.npmjs.org/ai/-/ai-6.0.67.tgz} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, tarball: https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: optional: true + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==, tarball: https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, tarball: https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz} engines: {node: '>=6'} @@ -1094,38 +1044,23 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, tarball: https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==, tarball: https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, tarball: https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz} engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz} engines: {node: '>=12'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==, tarball: https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, tarball: https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz} - engines: {node: '>= 8'} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, tarball: https://registry.npmjs.org/arg/-/arg-4.1.3.tgz} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==, tarball: https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz} + engines: {node: '>=14'} argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, tarball: https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, tarball: https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz} - engines: {node: '>= 0.4'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, tarball: https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz} array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, tarball: https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz} @@ -1135,132 +1070,116 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, tarball: https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz} engines: {node: '>=12'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, tarball: https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz} - - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==, tarball: https://registry.npmjs.org/axios/-/axios-1.7.7.tgz} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, tarball: https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz} - engines: {node: '>= 0.4'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, tarball: https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz} + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==, tarball: https://registry.npmjs.org/ast-kit/-/ast-kit-3.0.0-beta.1.tgz} + engines: {node: '>=20.19.0'} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, tarball: https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz} + baseline-browser-mapping@2.10.21: + resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==, tarball: https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz} + engines: {node: '>=6.0.0'} + hasBin: true better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==, tarball: https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz} engines: {node: '>=4'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, tarball: https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz} - engines: {node: '>=8'} - - binary-search@1.3.6: - resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==, tarball: https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz} + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==, tarball: https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==, tarball: https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz} + engines: {node: '>=18'} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, tarball: https://registry.npmjs.org/braces/-/braces-3.0.3.tgz} engines: {node: '>=8'} - bundle-require@5.0.0: - resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==, tarball: https://registry.npmjs.org/bundle-require/-/bundle-require-5.0.0.tgz} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, tarball: https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz} + engines: {node: '>= 0.8'} cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, tarball: https://registry.npmjs.org/cac/-/cac-6.7.14.tgz} engines: {node: '>=8'} - camelcase@4.1.0: - resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==, tarball: https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz} - engines: {node: '>=4'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==, tarball: https://registry.npmjs.org/cac/-/cac-7.0.0.tgz} + engines: {node: '>=20.19.0'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, tarball: https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz} - engines: {node: '>=10'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, tarball: https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz} + engines: {node: '>= 0.4'} - chai@5.1.1: - resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==, tarball: https://registry.npmjs.org/chai/-/chai-5.1.1.tgz} - engines: {node: '>=12'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, tarball: https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz} + engines: {node: '>= 0.4'} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==, tarball: https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz} - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==, tarball: https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, tarball: https://registry.npmjs.org/chai/-/chai-5.3.3.tgz} + engines: {node: '>=18'} - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, tarball: https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz} - engines: {node: '>= 16'} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==, tarball: https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, tarball: https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz} - engines: {node: '>= 8.10.0'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==, tarball: https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz} + engines: {node: '>= 16'} ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==, tarball: https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz} engines: {node: '>=8'} - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, tarball: https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz} - - code-red@1.0.4: - resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==, tarball: https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==, tarball: https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz} + engines: {node: '>=20'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, tarball: https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz} - engines: {node: '>=7.0.0'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==, tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz} + engines: {node: '>=18'} - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, tarball: https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz} + engines: {node: '>= 0.6'} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, tarball: https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz} - engines: {node: '>= 0.8'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz} - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==, tarball: https://registry.npmjs.org/commander/-/commander-10.0.1.tgz} - engines: {node: '>=14'} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==, tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz} + engines: {node: '>=6.6.0'} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==, tarball: https://registry.npmjs.org/commander/-/commander-4.1.1.tgz} - engines: {node: '>= 6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==, tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz} + engines: {node: '>= 0.6'} - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==, tarball: https://registry.npmjs.org/consola/-/consola-3.2.3.tgz} - engines: {node: ^14.18.0 || >=16.10.0} + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==, tarball: https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, tarball: https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==, tarball: https://registry.npmjs.org/cors/-/cors-2.8.6.tgz} + engines: {node: '>= 0.10'} - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==, tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz} + cron-schedule@6.0.0: + resolution: {integrity: sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==, tarball: https://registry.npmjs.org/cron-schedule/-/cron-schedule-6.0.0.tgz} + engines: {node: '>=20'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz} engines: {node: '>= 8'} - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==, tarball: https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, tarball: https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz} - dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==, tarball: https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz} - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==, tarball: https://registry.npmjs.org/debug/-/debug-4.3.7.tgz} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, tarball: https://registry.npmjs.org/debug/-/debug-4.4.3.tgz} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1268,113 +1187,155 @@ packages: supports-color: optional: true - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, tarball: https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz} - engines: {node: '>=0.10.0'} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, tarball: https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz} engines: {node: '>=6'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, tarball: https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz} - engines: {node: '>=0.4.0'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, tarball: https://registry.npmjs.org/defu/-/defu-6.1.4.tgz} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, tarball: https://registry.npmjs.org/depd/-/depd-2.0.0.tgz} + engines: {node: '>= 0.8'} detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, tarball: https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz} engines: {node: '>=8'} - diff-match-patch@1.0.5: - resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==, tarball: https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==, tarball: https://registry.npmjs.org/diff/-/diff-4.0.2.tgz} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, tarball: https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz} engines: {node: '>=8'} - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==, tarball: https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==, tarball: https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz} engines: {node: '>=12'} dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==, tarball: https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz} engines: {node: '>=10'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, tarball: https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz} + dts-resolver@2.1.3: + resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==, tarball: https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.3.tgz} + engines: {node: '>=20.19.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, tarball: https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, tarball: https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, tarball: https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz} + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, tarball: https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, tarball: https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==, tarball: https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz} + engines: {node: '>=14'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==, tarball: https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz} + engines: {node: '>= 0.8'} enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==, tarball: https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz} engines: {node: '>=8.6'} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, tarball: https://registry.npmjs.org/entities/-/entities-4.5.0.tgz} - engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, tarball: https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, tarball: https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, tarball: https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, tarball: https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz} + engines: {node: '>= 0.4'} esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz} engines: {node: '>=12'} hasBin: true - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz} engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, tarball: https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, tarball: https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz} engines: {node: '>=4'} hasBin: true - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, tarball: https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, tarball: https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==, tarball: https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz} - engines: {node: '>=6'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, tarball: https://registry.npmjs.org/etag/-/etag-1.8.1.tgz} + engines: {node: '>= 0.6'} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, tarball: https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz} + event-target-polyfill@0.0.4: + resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==, tarball: https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz} - eventsource-parser@1.1.2: - resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==, tarball: https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz} - engines: {node: '>=14.18'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==, tarball: https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz} + engines: {node: '>=18.0.0'} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, tarball: https://registry.npmjs.org/execa/-/execa-5.1.1.tgz} - engines: {node: '>=10'} + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==, tarball: https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz} + engines: {node: '>=18.0.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==, tarball: https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz} + engines: {node: '>=12.0.0'} + + express-rate-limit@8.4.0: + resolution: {integrity: sha512-gDK8yiqKxrGta+3WtON59arrrw6GLmadA1qoFgYXzdcch8fmKDID2XqO8itsi3f1wufXYPT51387dN6cvVBS3Q==, tarball: https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.0.tgz} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' - expr-eval@2.0.2: - resolution: {integrity: sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==, tarball: https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==, tarball: https://registry.npmjs.org/express/-/express-5.2.1.tgz} + engines: {node: '>= 18'} extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==, tarball: https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz} - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==, tarball: https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz} - engines: {node: '>=4'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==, tarball: https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, tarball: https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz} engines: {node: '>=8.6.0'} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==, tarball: https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, tarball: https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz} - fdir@6.4.0: - resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==, tarball: https://registry.npmjs.org/fdir/-/fdir-6.4.0.tgz} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, tarball: https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, tarball: https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1385,37 +1346,21 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, tarball: https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==, tarball: https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz} + engines: {node: '>= 18.0.0'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, tarball: https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz} engines: {node: '>=8'} - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==, tarball: https://registry.npmjs.org/flat/-/flat-5.0.2.tgz} - hasBin: true - - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==, tarball: https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==, tarball: https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz} - engines: {node: '>=14'} - - form-data-encoder@1.7.2: - resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==, tarball: https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz} - - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, tarball: https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz} - engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==, tarball: https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz} + engines: {node: '>= 0.6'} - formdata-node@4.4.1: - resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==, tarball: https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz} - engines: {node: '>= 12.20'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==, tarball: https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz} + engines: {node: '>= 0.8'} fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==, tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz} @@ -1430,64 +1375,97 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==, tarball: https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, tarball: https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, tarball: https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz} - engines: {node: '>=10'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, tarball: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, tarball: https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==, tarball: https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, tarball: https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, tarball: https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==, tarball: https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz} engines: {node: '>= 6'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, tarball: https://registry.npmjs.org/glob/-/glob-10.4.5.tgz} - hasBin: true - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, tarball: https://registry.npmjs.org/globby/-/globby-11.1.0.tgz} engines: {node: '>=10'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, tarball: https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, tarball: https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz} - human-id@1.0.2: - resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==, tarball: https://registry.npmjs.org/human-id/-/human-id-1.0.2.tgz} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, tarball: https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==, tarball: https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz} + engines: {node: '>= 0.4'} + + hono@4.12.15: + resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==, tarball: https://registry.npmjs.org/hono/-/hono-4.12.15.tgz} + engines: {node: '>=16.9.0'} + + hookable@6.1.0: + resolution: {integrity: sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw==, tarball: https://registry.npmjs.org/hookable/-/hookable-6.1.0.tgz} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, tarball: https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz} - engines: {node: '>=10.17.0'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==, tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz} + engines: {node: '>= 0.8'} - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==, tarball: https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz} + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==, tarball: https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz} + hasBin: true - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==, tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz} engines: {node: '>=0.10.0'} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, tarball: https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz} engines: {node: '>= 4'} - infobox-parser@3.6.4: - resolution: {integrity: sha512-d2lTlxKZX7WsYxk9/UPt51nkmZv5tbC75SSw4hfHqZ3LpRAn6ug0oru9xI2X+S78va3aUAze3xl/UqMuwLmJUw==, tarball: https://registry.npmjs.org/infobox-parser/-/infobox-parser-3.6.4.tgz} + import-without-cache@0.2.5: + resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==, tarball: https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.2.5.tgz} + engines: {node: '>=20.19.0'} - is-any-array@2.0.1: - resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==, tarball: https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, tarball: https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, tarball: https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz} - engines: {node: '>=8'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==, tarball: https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==, tarball: https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz} + engines: {node: '>= 0.10'} is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, tarball: https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, tarball: https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz} engines: {node: '>=0.10.0'} @@ -1496,12 +1474,8 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, tarball: https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz} engines: {node: '>=0.12.0'} - is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==, tarball: https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, tarball: https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz} - engines: {node: '>=8'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==, tarball: https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz} is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==, tarball: https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz} @@ -1514,94 +1488,72 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, tarball: https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, tarball: https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==, tarball: https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz} - engines: {node: '>=10'} + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==, tarball: https://registry.npmjs.org/jose/-/jose-6.2.2.tgz} - js-tiktoken@1.0.15: - resolution: {integrity: sha512-65ruOWWXDEZHHbAo7EjOcNxOGasQKbL4Fq3jEr2xsCqSsoOo6VVSqzWQb6PRIqypFSDcma4jO90YP0w5X8qVXQ==, tarball: https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.15.tgz} + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==, tarball: https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, tarball: https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==, tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz} hasBin: true - json-schema-to-ts@3.1.1: - resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==, tarball: https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz} - engines: {node: '>=16'} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, tarball: https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==, tarball: https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz} json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, tarball: https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz} - jsondiffpatch@0.6.0: - resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==, tarball: https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz} - engines: {node: ^18.0.0 || >=20.0.0} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, tarball: https://registry.npmjs.org/json5/-/json5-2.2.3.tgz} + engines: {node: '>=6'} hasBin: true jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz} - langsmith@0.1.61: - resolution: {integrity: sha512-XQE4KPScwPmdaT0mWDzhNxj9gvqXUR+C7urLA0QFi27XeoQdm17eYpudenn4wxC0gIyUJutQCyuYJpfwlT5JnQ==, tarball: https://registry.npmjs.org/langsmith/-/langsmith-0.1.61.tgz} - peerDependencies: - openai: '*' - peerDependenciesMeta: - openai: - optional: true - - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==, tarball: https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, tarball: https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==, tarball: https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - locate-character@3.0.0: - resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==, tarball: https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, tarball: https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz} engines: {node: '>=8'} - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==, tarball: https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz} - lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==, tarball: https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz} - hasBin: true - - loupe@3.1.1: - resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==, tarball: https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, tarball: https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz} - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, tarball: https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz} - magic-string@0.30.11: - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==, tarball: https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, tarball: https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, tarball: https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz} + engines: {node: '>= 0.4'} - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==, tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==, tarball: https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz} + engines: {node: '>= 0.8'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, tarball: https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==, tarball: https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz} + engines: {node: '>=18'} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, tarball: https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz} @@ -1615,36 +1567,20 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, tarball: https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz} engines: {node: '>= 0.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, tarball: https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz} - engines: {node: '>=6'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, tarball: https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz} - engines: {node: '>=16 || 14 >=14.17'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, tarball: https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz} - engines: {node: '>=16 || 14 >=14.17'} - - ml-array-mean@1.1.6: - resolution: {integrity: sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==, tarball: https://registry.npmjs.org/ml-array-mean/-/ml-array-mean-1.1.6.tgz} - - ml-array-sum@1.1.6: - resolution: {integrity: sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw==, tarball: https://registry.npmjs.org/ml-array-sum/-/ml-array-sum-1.1.6.tgz} - - ml-distance-euclidean@2.0.0: - resolution: {integrity: sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q==, tarball: https://registry.npmjs.org/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz} - - ml-distance@4.0.1: - resolution: {integrity: sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw==, tarball: https://registry.npmjs.org/ml-distance/-/ml-distance-4.0.1.tgz} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==, tarball: https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz} + engines: {node: '>=18'} - ml-tree-similarity@1.0.0: - resolution: {integrity: sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==, tarball: https://registry.npmjs.org/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz} + mimetext@3.0.28: + resolution: {integrity: sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==, tarball: https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==, tarball: https://registry.npmjs.org/mri/-/mri-1.2.0.tgz} @@ -1653,26 +1589,19 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, tarball: https://registry.npmjs.org/ms/-/ms-2.1.3.tgz} - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==, tarball: https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz} - hasBin: true - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==, tarball: https://registry.npmjs.org/mz/-/mz-2.7.0.tgz} - - nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + nanoid@5.1.9: + resolution: {integrity: sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==, tarball: https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz} + engines: {node: ^18 || >=20} hasBin: true - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==, tarball: https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz} - engines: {node: '>=10.5.0'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==, tarball: https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz} + engines: {node: '>= 0.6'} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, tarball: https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz} @@ -1683,42 +1612,26 @@ packages: encoding: optional: true - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, tarball: https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, tarball: https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz} - engines: {node: '>=8'} - - num-sort@2.1.0: - resolution: {integrity: sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg==, tarball: https://registry.npmjs.org/num-sort/-/num-sort-2.1.0.tgz} - engines: {node: '>=8'} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, tarball: https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz} engines: {node: '>=0.10.0'} - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==, tarball: https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz} - engines: {node: '>= 6'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, tarball: https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz} + engines: {node: '>= 0.4'} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, tarball: https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz} - engines: {node: '>=6'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==, tarball: https://registry.npmjs.org/obug/-/obug-2.1.1.tgz} - openai@4.67.1: - resolution: {integrity: sha512-2YbRFy6qaYRJabK2zLMn4txrB2xBy0KP5g/eoqeSPTT31mIJMnkT75toagvfE555IKa2RdrzJrZwdDsUipsAMw==, tarball: https://registry.npmjs.org/openai/-/openai-4.67.1.tgz} - hasBin: true - peerDependencies: - zod: ^3.23.8 - peerDependenciesMeta: - zod: - optional: true + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, tarball: https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz} + engines: {node: '>= 0.8'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==, tarball: https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz} - engines: {node: '>=0.10.0'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, tarball: https://registry.npmjs.org/once/-/once-1.4.0.tgz} outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==, tarball: https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz} @@ -1727,10 +1640,6 @@ packages: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==, tarball: https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz} engines: {node: '>=8'} - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==, tarball: https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, tarball: https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz} engines: {node: '>=6'} @@ -1743,27 +1652,29 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==, tarball: https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz} engines: {node: '>=6'} - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==, tarball: https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz} - engines: {node: '>=8'} - - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==, tarball: https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==, tarball: https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz} - engines: {node: '>=8'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, tarball: https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, tarball: https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz} + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==, tarball: https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, tarball: https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz} + engines: {node: '>= 0.8'} + + partyserver@0.4.1: + resolution: {integrity: sha512-StSs0oY8RmTxjGNil7VbCG4gnTN+4rYX20fiUIItAxTPpr/5rPDZT6PIvMROkk9M1Gn7GzE1wuQXwhxceaGhXA==, tarball: https://registry.npmjs.org/partyserver/-/partyserver-0.4.1.tgz} + peerDependencies: + '@cloudflare/workers-types': ^4.20240729.0 - package-manager-detector@0.2.1: - resolution: {integrity: sha512-/hVW2fZvAdEas+wyKh0SnlZ2mx0NIa1+j11YaQkogEJkcMErbwchHCuo8z7lEtajZJQZ6rgZNVTWMVVd71Bjng==, tarball: https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.1.tgz} + partysocket@1.1.18: + resolution: {integrity: sha512-SyuvH9VavWOSa14v6dYdp3yfSUDII4BQB1+TkGOFBkjfZKjnDBiba4fhdhwBlqGBkqw4ea3gTA1DYhSffX24Wg==, tarball: https://registry.npmjs.org/partysocket/-/partysocket-1.1.18.tgz} + peerDependencies: + react: '>=17' + peerDependenciesMeta: + react: + optional: true path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, tarball: https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz} @@ -1773,9 +1684,8 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, tarball: https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz} engines: {node: '>=8'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, tarball: https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz} - engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==, tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, tarball: https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz} @@ -1784,52 +1694,38 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==, tarball: https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz} - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, tarball: https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz} - engines: {node: '>= 14.16'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} - periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==, tarball: https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, tarball: https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz} + engines: {node: '>= 14.16'} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz} + engines: {node: '>=12'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==, tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz} engines: {node: '>=12'} pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, tarball: https://registry.npmjs.org/pify/-/pify-4.0.1.tgz} engines: {node: '>=6'} - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==, tarball: https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz} - engines: {node: '>= 6'} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==, tarball: https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==, tarball: https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz} + engines: {node: '>=16.20.0'} - postcss@8.4.47: - resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==, tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, tarball: https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz} engines: {node: ^10 || ^12 || >=14} prettier@2.8.8: @@ -1837,87 +1733,145 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, tarball: https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==, tarball: https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz} + engines: {node: '>= 0.10'} - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==, tarball: https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==, tarball: https://registry.npmjs.org/qs/-/qs-6.15.1.tgz} + engines: {node: '>=0.6'} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, tarball: https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz} - engines: {node: '>=6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==, tarball: https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==, tarball: https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, tarball: https://registry.npmjs.org/react/-/react-18.3.1.tgz} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, tarball: https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==, tarball: https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz} + engines: {node: '>= 0.10'} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==, tarball: https://registry.npmjs.org/react/-/react-19.2.5.tgz} engines: {node: '>=0.10.0'} read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==, tarball: https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz} engines: {node: '>=6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, tarball: https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz} - engines: {node: '>=8.10.0'} - - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, tarball: https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, tarball: https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz} + engines: {node: '>=0.10.0'} resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, tarball: https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz} engines: {node: '>=8'} - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==, tarball: https://registry.npmjs.org/retry/-/retry-0.13.1.tgz} - engines: {node: '>= 4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, tarball: https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz} - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, tarball: https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, tarball: https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==, tarball: https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz} + rolldown-plugin-dts@0.23.2: + resolution: {integrity: sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==, tarball: https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.23.2.tgz} + engines: {node: '>=20.19.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0-rc.12 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==, tarball: https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==, tarball: https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==, tarball: https://registry.npmjs.org/router/-/router-2.2.0.tgz} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz} safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, tarball: https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz} - secure-json-parse@2.7.0: - resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==, tarball: https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, tarball: https://registry.npmjs.org/semver/-/semver-6.3.1.tgz} + hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==, tarball: https://registry.npmjs.org/semver/-/semver-7.6.3.tgz} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz} engines: {node: '>=10'} hasBin: true - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==, tarball: https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz} - engines: {node: '>=0.10.0'} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.4.tgz} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==, tarball: https://registry.npmjs.org/send/-/send-1.2.1.tgz} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==, tarball: https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, tarball: https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, tarball: https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==, tarball: https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, tarball: https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz} engines: {node: '>=8'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==, tarball: https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, tarball: https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, tarball: https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, tarball: https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz} engines: {node: '>=14'} @@ -1930,96 +1884,58 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, tarball: https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz} engines: {node: '>=0.10.0'} - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==, tarball: https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz} - engines: {node: '>= 8'} - - spawndamnit@2.0.0: - resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==, tarball: https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==, tarball: https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, tarball: https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz} - sswr@2.1.0: - resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==, tarball: https://registry.npmjs.org/sswr/-/sswr-2.1.0.tgz} - peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, tarball: https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==, tarball: https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==, tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz} + engines: {node: '>= 0.8'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, tarball: https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz} - engines: {node: '>=8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, tarball: https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, tarball: https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz} - engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, tarball: https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz} + engines: {node: '>=18'} strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==, tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz} engines: {node: '>=12'} strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, tarball: https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz} engines: {node: '>=4'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, tarball: https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz} - engines: {node: '>=6'} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==, tarball: https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - svelte@4.2.19: - resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==, tarball: https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz} - engines: {node: '>=16'} - - swr@2.2.5: - resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==, tarball: https://registry.npmjs.org/swr/-/swr-2.2.5.tgz} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - - swrev@4.0.0: - resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==, tarball: https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz} - - swrv@1.0.4: - resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==, tarball: https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz} - peerDependencies: - vue: '>=3.2.26 < 4' - term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==, tarball: https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz} engines: {node: '>=8'} - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==, tarball: https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==, tarball: https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, tarball: https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz} - tinyexec@0.3.0: - resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==, tarball: https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, tarball: https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz} - tinyglobby@0.2.9: - resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==, tarball: https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.9.tgz} + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==, tarball: https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, tarball: https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz} engines: {node: '>=12.0.0'} - tinypool@1.0.1: - resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==, tarball: https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, tarball: https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@1.2.0: @@ -2030,105 +1946,107 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==, tarball: https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz} engines: {node: '>=14.0.0'} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==, tarball: https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz} - engines: {node: '>=0.6.0'} - - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==, tarball: https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, tarball: https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, tarball: https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz} + engines: {node: '>=0.6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, tarball: https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz} - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==, tarball: https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz} - tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, tarball: https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz} hasBin: true - ts-algebra@2.0.0: - resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==, tarball: https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==, tarball: https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz} - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==, tarball: https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz} + tsdown@0.21.7: + resolution: {integrity: sha512-ukKIxKQzngkWvOYJAyptudclkm4VQqbjq+9HF5K5qDO8GJsYtMh8gIRwicbnZEnvFPr6mquFwYAVZ8JKt3rY2g==, tarball: https://registry.npmjs.org/tsdown/-/tsdown-0.21.7.tgz} + engines: {node: '>=20.19.0'} hasBin: true peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.7 + '@tsdown/exe': 0.21.7 + '@vitejs/devtools': '*' + publint: ^0.3.0 + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 peerDependenciesMeta: - '@swc/core': + '@arethetypeswrong/core': optional: true - '@swc/wasm': + '@tsdown/css': optional: true - - tsup@8.3.0: - resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==, tarball: https://registry.npmjs.org/tsup/-/tsup-8.3.0.tgz} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': + '@tsdown/exe': optional: true - '@swc/core': + '@vitejs/devtools': optional: true - postcss: + publint: optional: true typescript: optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz} - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==, tarball: https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==, tarball: https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz} + engines: {node: '>=18.0.0'} + hasBin: true + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==, tarball: https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, tarball: https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz} engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==, tarball: https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz} universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, tarball: https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz} engines: {node: '>= 4.0.0'} - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, tarball: https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz} + engines: {node: '>= 0.8'} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==, tarball: https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz} + unrun@0.2.34: + resolution: {integrity: sha512-LyaghRBR++r7svhDK6tnDz2XaYHWdneBOA0jbS8wnRsHerI9MFljX4fIiTgbbNbEVzZ0C9P1OjWLLe1OqoaaEw==, tarball: https://registry.npmjs.org/unrun/-/unrun-0.2.34.tgz} + engines: {node: '>=20.19.0'} hasBin: true + peerDependencies: + synckit: ^0.11.11 + peerDependenciesMeta: + synckit: + optional: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==, tarball: https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz} hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==, tarball: https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, tarball: https://registry.npmjs.org/vary/-/vary-1.1.2.tgz} + engines: {node: '>= 0.8'} - vite-node@2.1.2: - resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==, tarball: https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==, tarball: https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.8: - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==, tarball: https://registry.npmjs.org/vite/-/vite-5.4.8.tgz} + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==, tarball: https://registry.npmjs.org/vite/-/vite-5.4.21.tgz} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2158,15 +2076,15 @@ packages: terser: optional: true - vitest@2.1.2: - resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==, tarball: https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz} + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==, tarball: https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.2 - '@vitest/ui': 2.1.2 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2183,34 +2101,12 @@ packages: jsdom: optional: true - vue@3.5.11: - resolution: {integrity: sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==, tarball: https://registry.npmjs.org/vue/-/vue-3.5.11.tgz} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - web-streams-polyfill@4.0.0-beta.3: - resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==, tarball: https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz} - engines: {node: '>= 14'} - webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz} - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz} - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz} - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, tarball: https://registry.npmjs.org/which/-/which-1.3.1.tgz} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, tarball: https://registry.npmjs.org/which/-/which-2.0.2.tgz} engines: {node: '>= 8'} @@ -2221,151 +2117,262 @@ packages: engines: {node: '>=8'} hasBin: true - wikipedia@2.1.2: - resolution: {integrity: sha512-RAYaMpXC9/E873RaSEtlEa8dXK4e0p5k98GKOd210MtkE5emm6fcnwD+N6ZA4cuffjDWagvhaQKtp/mGp2BOVQ==, tarball: https://registry.npmjs.org/wikipedia/-/wikipedia-2.1.2.tgz} - engines: {node: '>=10'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz} + engines: {node: '>=18'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz} - engines: {node: '>=10'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, tarball: https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz} - engines: {node: '>=12'} + xstate@5.26.0: + resolution: {integrity: sha512-Fvi9VBoqHgsGYLU2NTag8xDTWtKqUC0+ue7EAhBNBb06wf620QEy05upBaEI1VLMzIn63zugLV8nHb69ZUWYAA==, tarball: https://registry.npmjs.org/xstate/-/xstate-5.26.0.tgz} - xstate@5.18.2: - resolution: {integrity: sha512-hab5VOe29D0agy8/7dH1lGw+7kilRQyXwpaChoMu4fe6rDP+nsHYhDYKfS2O4iXE7myA98TW6qMEudj/8NXEkA==, tarball: https://registry.npmjs.org/xstate/-/xstate-5.18.2.tgz} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, tarball: https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz} + engines: {node: '>=10'} - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==, tarball: https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, tarball: https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==, tarball: https://registry.npmjs.org/yn/-/yn-3.1.1.tgz} - engines: {node: '>=6'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==, tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} - zod-to-json-schema@3.23.2: - resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==, tarball: https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz} - peerDependencies: - zod: ^3.23.3 + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==, tarball: https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} - zod-to-json-schema@3.23.3: - resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==, tarball: https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.3.tgz} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==, tarball: https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz} peerDependencies: - zod: ^3.23.3 + zod: ^3.25.28 || ^4 - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==, tarball: https://registry.npmjs.org/zod/-/zod-3.23.8.tgz} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==, tarball: https://registry.npmjs.org/zod/-/zod-4.3.6.tgz} snapshots: - '@ai-sdk/openai@0.0.40(zod@3.23.8)': + '@ai-sdk/gateway@3.0.32(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 0.0.14 - '@ai-sdk/provider-utils': 1.0.5(zod@3.23.8) - zod: 3.23.8 + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@vercel/oidc': 3.1.0 + zod: 4.3.6 - '@ai-sdk/provider-utils@1.0.20(zod@3.23.8)': + '@ai-sdk/openai@3.0.25(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 0.0.24 - eventsource-parser: 1.1.2 - nanoid: 3.3.6 - secure-json-parse: 2.7.0 - optionalDependencies: - zod: 3.23.8 + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + zod: 4.3.6 - '@ai-sdk/provider-utils@1.0.5(zod@3.23.8)': + '@ai-sdk/provider-utils@4.0.13(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 0.0.14 - eventsource-parser: 1.1.2 - nanoid: 3.3.6 - secure-json-parse: 2.7.0 - optionalDependencies: - zod: 3.23.8 + '@ai-sdk/provider': 3.0.7 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.3.6 - '@ai-sdk/provider@0.0.14': + '@ai-sdk/provider@3.0.7': dependencies: json-schema: 0.4.0 - '@ai-sdk/provider@0.0.24': + '@babel/code-frame@7.29.0': dependencies: - json-schema: 0.4.0 + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@ai-sdk/react@0.0.62(react@18.3.1)(zod@3.23.8)': + '@babel/generator@7.29.1': dependencies: - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - swr: 2.2.5(react@18.3.1) - optionalDependencies: - react: 18.3.1 - zod: 3.23.8 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@8.0.0-rc.3': + dependencies: + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 - '@ai-sdk/solid@0.0.49(zod@3.23.8)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 transitivePeerDependencies: - - zod + - supports-color - '@ai-sdk/svelte@0.0.51(svelte@4.2.19)(zod@3.23.8)': + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - sswr: 2.1.0(svelte@4.2.19) - optionalDependencies: - svelte: 4.2.19 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - - zod + - supports-color - '@ai-sdk/ui-utils@0.0.46(zod@3.23.8)': + '@babel/helper-module-imports@7.28.6': dependencies: - '@ai-sdk/provider': 0.0.24 - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - json-schema: 0.4.0 - secure-json-parse: 2.7.0 - zod-to-json-schema: 3.23.2(zod@3.23.8) - optionalDependencies: - zod: 3.23.8 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color - '@ai-sdk/vue@0.0.54(vue@3.5.11(typescript@5.6.2))(zod@3.23.8)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - swrv: 1.0.4(vue@3.5.11(typescript@5.6.2)) - optionalDependencies: - vue: 3.5.11(typescript@5.6.2) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-string-parser@8.0.0-rc.3': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-identifier@8.0.0-rc.3': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/parser@8.0.0-rc.3': + dependencies: + '@babel/types': 8.0.0-rc.3 + + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - - zod + - supports-color + + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@ampproject/remapping@2.3.0': + '@babel/runtime-corejs3@7.29.2': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + core-js-pure: 3.49.0 - '@babel/helper-string-parser@7.25.7': {} + '@babel/runtime@7.28.6': {} - '@babel/helper-validator-identifier@7.25.7': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 - '@babel/parser@7.25.7': + '@babel/traverse@7.29.0': dependencies: - '@babel/types': 7.25.7 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - '@babel/runtime@7.25.7': + '@babel/types@7.29.0': dependencies: - regenerator-runtime: 0.14.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.25.7': + '@babel/types@8.0.0-rc.3': dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 - '@changesets/apply-release-plan@7.0.5': + '@cfworker/json-schema@4.1.1': {} + + '@changesets/apply-release-plan@7.0.14': dependencies: - '@changesets/config': 3.0.3 + '@changesets/config': 3.1.2 '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.1 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 fs-extra: 7.0.1 @@ -2373,66 +2380,68 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.6.3 + semver: 7.7.3 - '@changesets/assemble-release-plan@6.0.4': + '@changesets/assemble-release-plan@6.0.9': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.6.3 + semver: 7.7.3 - '@changesets/changelog-git@0.2.0': + '@changesets/changelog-git@0.2.1': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 - '@changesets/changelog-github@0.5.0': + '@changesets/changelog-github@0.5.2': dependencies: - '@changesets/get-github-info': 0.6.0 - '@changesets/types': 6.0.0 + '@changesets/get-github-info': 0.7.0 + '@changesets/types': 6.1.0 dotenv: 8.6.0 transitivePeerDependencies: - encoding - '@changesets/cli@2.27.9': + '@changesets/cli@2.29.8(@types/node@20.19.30)': dependencies: - '@changesets/apply-release-plan': 7.0.5 - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.3 + '@changesets/apply-release-plan': 7.0.14 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.2 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 - '@changesets/get-release-plan': 4.0.4 - '@changesets/git': 3.0.1 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.14 + '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 - '@changesets/should-skip-package': 0.1.1 - '@changesets/types': 6.0.0 - '@changesets/write': 0.3.2 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@20.19.30) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 ci-info: 3.9.0 enquirer: 2.4.1 - external-editor: 3.1.0 fs-extra: 7.0.1 mri: 1.2.0 p-limit: 2.3.0 - package-manager-detector: 0.2.1 - picocolors: 1.1.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.6.3 - spawndamnit: 2.0.0 + semver: 7.7.3 + spawndamnit: 3.0.1 term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' - '@changesets/config@3.0.3': + '@changesets/config@3.1.2': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-dependents-graph': 2.1.3 '@changesets/logger': 0.1.1 - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 micromatch: 4.0.8 @@ -2441,314 +2450,323 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.2': + '@changesets/get-dependents-graph@2.1.3': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.0 - semver: 7.6.3 + picocolors: 1.1.1 + semver: 7.7.3 - '@changesets/get-github-info@0.6.0': + '@changesets/get-github-info@0.7.0': dependencies: dataloader: 1.4.0 node-fetch: 2.7.0 transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.4': + '@changesets/get-release-plan@4.0.14': dependencies: - '@changesets/assemble-release-plan': 6.0.4 - '@changesets/config': 3.0.3 - '@changesets/pre': 2.0.1 - '@changesets/read': 0.6.1 - '@changesets/types': 6.0.0 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.2 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@3.0.1': + '@changesets/git@3.0.4': dependencies: '@changesets/errors': 0.2.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.8 - spawndamnit: 2.0.0 + spawndamnit: 3.0.1 '@changesets/logger@0.1.1': dependencies: - picocolors: 1.1.0 + picocolors: 1.1.1 - '@changesets/parse@0.4.0': + '@changesets/parse@0.4.2': dependencies: - '@changesets/types': 6.0.0 - js-yaml: 3.14.1 + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 - '@changesets/pre@2.0.1': + '@changesets/pre@2.0.2': dependencies: '@changesets/errors': 0.2.0 - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.1': + '@changesets/read@0.6.6': dependencies: - '@changesets/git': 3.0.1 + '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.0 - '@changesets/types': 6.0.0 + '@changesets/parse': 0.4.2 + '@changesets/types': 6.1.0 fs-extra: 7.0.1 p-filter: 2.1.0 - picocolors: 1.1.0 + picocolors: 1.1.1 - '@changesets/should-skip-package@0.1.1': + '@changesets/should-skip-package@0.1.2': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 '@changesets/types@4.1.0': {} - '@changesets/types@6.0.0': {} + '@changesets/types@6.1.0': {} - '@changesets/write@0.3.2': + '@changesets/write@0.4.0': dependencies: - '@changesets/types': 6.0.0 + '@changesets/types': 6.1.0 fs-extra: 7.0.1 - human-id: 1.0.2 + human-id: 4.1.3 prettier: 2.8.8 - '@cspotcode/source-map-support@0.8.1': + '@cloudflare/workers-types@4.20260424.1': {} + + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': dependencies: - '@jridgewell/trace-mapping': 0.3.9 + tslib: 2.8.1 + optional: true '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.1': + '@esbuild/aix-ppc64@0.27.7': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.1': + '@esbuild/android-arm64@0.27.7': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.1': + '@esbuild/android-arm@0.27.7': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.1': + '@esbuild/android-x64@0.27.7': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.1': + '@esbuild/darwin-arm64@0.27.7': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.1': + '@esbuild/darwin-x64@0.27.7': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.1': + '@esbuild/freebsd-arm64@0.27.7': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.1': + '@esbuild/freebsd-x64@0.27.7': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.1': + '@esbuild/linux-arm64@0.27.7': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.1': + '@esbuild/linux-arm@0.27.7': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.1': + '@esbuild/linux-ia32@0.27.7': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.1': + '@esbuild/linux-loong64@0.27.7': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.1': + '@esbuild/linux-mips64el@0.27.7': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.1': + '@esbuild/linux-ppc64@0.27.7': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.1': + '@esbuild/linux-riscv64@0.27.7': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.1': + '@esbuild/linux-s390x@0.27.7': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.1': + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.1': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.23.1': + '@esbuild/openbsd-arm64@0.27.7': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.1': + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.1': + '@esbuild/sunos-x64@0.27.7': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.1': + '@esbuild/win32-arm64@0.27.7': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.1': + '@esbuild/win32-ia32@0.27.7': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.1': + '@esbuild/win32-x64@0.27.7': optional: true - '@isaacs/cliui@8.0.2': + '@hono/node-server@1.19.14(hono@4.12.15)': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + hono: 4.12.15 - '@jridgewell/gen-mapping@0.3.5': + '@inquirer/external-editor@1.0.3(@types/node@20.19.30)': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 20.19.30 - '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.9': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@langchain/community@0.0.53(openai@4.67.1(zod@3.23.8))': - dependencies: - '@langchain/core': 0.1.63(openai@4.67.1(zod@3.23.8)) - '@langchain/openai': 0.0.28 - expr-eval: 2.0.2 - flat: 5.0.2 - langsmith: 0.1.61(openai@4.67.1(zod@3.23.8)) - uuid: 9.0.1 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - encoding - - openai - - '@langchain/core@0.1.63(openai@4.67.1(zod@3.23.8))': - dependencies: - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.15 - langsmith: 0.1.61(openai@4.67.1(zod@3.23.8)) - ml-distance: 4.0.1 - mustache: 4.2.0 - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 9.0.1 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - openai - - '@langchain/openai@0.0.28': - dependencies: - '@langchain/core': 0.1.63(openai@4.67.1(zod@3.23.8)) - js-tiktoken: 1.0.15 - openai: 4.67.1(zod@3.23.8) - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - encoding + '@jridgewell/sourcemap-codec': 1.5.5 '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.28.6 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.28.6 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 globby: 11.1.0 read-yaml-file: 1.1.0 + '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.15) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.4.0(express@5.2.1) + hono: 4.12.15 + jose: 6.2.2 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2759,421 +2777,441 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.20.1 '@opentelemetry/api@1.9.0': {} - '@pkgjs/parseargs@0.11.0': + '@oxc-project/types@0.122.0': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rolldown/binding-darwin-x64@1.0.0-rc.12': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.28.6)(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(vite@5.4.21(@types/node@20.19.30))': + dependencies: + '@babel/core': 7.29.0 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + optionalDependencies: + '@babel/runtime': 7.28.6 + vite: 5.4.21(@types/node@20.19.30) + + '@rolldown/pluginutils@1.0.0-rc.12': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-android-arm64@4.57.1': optional: true - '@tsconfig/node10@1.0.11': {} + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true - '@tsconfig/node12@1.0.11': {} + '@rollup/rollup-darwin-x64@4.57.1': + optional: true - '@tsconfig/node14@1.0.3': {} + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true - '@tsconfig/node16@1.0.4': {} + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true - '@types/diff-match-patch@1.0.36': {} + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true - '@types/estree@1.0.6': {} + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true - '@types/node-fetch@2.6.11': - dependencies: - '@types/node': 20.16.10 - form-data: 4.0.0 + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true - '@types/node@12.20.55': {} + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true - '@types/node@18.19.54': - dependencies: - undici-types: 5.26.5 + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true - '@types/node@20.16.10': - dependencies: - undici-types: 6.19.8 + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true - '@types/object-hash@3.0.6': {} + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true - '@types/retry@0.12.0': {} + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true - '@types/uuid@10.0.0': {} + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true - '@vitest/expect@2.1.2': - dependencies: - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 - chai: 5.1.1 - tinyrainbow: 1.2.0 + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.8(@types/node@20.16.10))': - dependencies: - '@vitest/spy': 2.1.2 - estree-walker: 3.0.3 - magic-string: 0.30.11 - optionalDependencies: - vite: 5.4.8(@types/node@20.16.10) + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true - '@vitest/pretty-format@2.1.2': - dependencies: - tinyrainbow: 1.2.0 + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true - '@vitest/runner@2.1.2': - dependencies: - '@vitest/utils': 2.1.2 - pathe: 1.1.2 + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true - '@vitest/snapshot@2.1.2': - dependencies: - '@vitest/pretty-format': 2.1.2 - magic-string: 0.30.11 - pathe: 1.1.2 + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true - '@vitest/spy@2.1.2': - dependencies: - tinyspy: 3.0.2 + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true - '@vitest/utils@2.1.2': - dependencies: - '@vitest/pretty-format': 2.1.2 - loupe: 3.1.1 - tinyrainbow: 1.2.0 + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true - '@vue/compiler-core@3.5.11': - dependencies: - '@babel/parser': 7.25.7 - '@vue/shared': 3.5.11 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true - '@vue/compiler-dom@3.5.11': - dependencies: - '@vue/compiler-core': 3.5.11 - '@vue/shared': 3.5.11 + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true - '@vue/compiler-sfc@3.5.11': - dependencies: - '@babel/parser': 7.25.7 - '@vue/compiler-core': 3.5.11 - '@vue/compiler-dom': 3.5.11 - '@vue/compiler-ssr': 3.5.11 - '@vue/shared': 3.5.11 - estree-walker: 2.0.2 - magic-string: 0.30.11 - postcss: 8.4.47 - source-map-js: 1.2.1 + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true - '@vue/compiler-ssr@3.5.11': - dependencies: - '@vue/compiler-dom': 3.5.11 - '@vue/shared': 3.5.11 + '@standard-schema/spec@1.1.0': {} - '@vue/reactivity@3.5.11': - dependencies: - '@vue/shared': 3.5.11 + '@statelyai/graph@0.11.0(zod@4.3.6)': + optionalDependencies: + zod: 4.3.6 - '@vue/runtime-core@3.5.11': + '@tybys/wasm-util@0.10.1': dependencies: - '@vue/reactivity': 3.5.11 - '@vue/shared': 3.5.11 + tslib: 2.8.1 + optional: true - '@vue/runtime-dom@3.5.11': - dependencies: - '@vue/reactivity': 3.5.11 - '@vue/runtime-core': 3.5.11 - '@vue/shared': 3.5.11 - csstype: 3.1.3 + '@types/estree@1.0.8': {} + + '@types/jsesc@2.5.1': {} - '@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2))': + '@types/node@12.20.55': {} + + '@types/node@20.19.30': dependencies: - '@vue/compiler-ssr': 3.5.11 - '@vue/shared': 3.5.11 - vue: 3.5.11(typescript@5.6.2) + undici-types: 6.21.0 - '@vue/shared@3.5.11': {} + '@vercel/oidc@3.1.0': {} + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 - '@xstate/graph@2.0.1(xstate@5.18.2)': + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.30))': dependencies: - xstate: 5.18.2 + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@20.19.30) - abort-controller@3.0.0: + '@vitest/pretty-format@2.1.9': dependencies: - event-target-shim: 5.0.1 + tinyrainbow: 1.2.0 - acorn-walk@8.3.4: + '@vitest/runner@2.1.9': dependencies: - acorn: 8.12.1 + '@vitest/utils': 2.1.9 + pathe: 1.1.2 - acorn@8.12.1: {} + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 - agentkeepalive@4.5.0: + '@vitest/spy@2.1.9': dependencies: - humanize-ms: 1.2.1 + tinyspy: 3.0.2 - ai@3.4.9(openai@4.67.1(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.6.2))(zod@3.23.8): + '@vitest/utils@2.1.9': dependencies: - '@ai-sdk/provider': 0.0.24 - '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) - '@ai-sdk/react': 0.0.62(react@18.3.1)(zod@3.23.8) - '@ai-sdk/solid': 0.0.49(zod@3.23.8) - '@ai-sdk/svelte': 0.0.51(svelte@4.2.19)(zod@3.23.8) - '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - '@ai-sdk/vue': 0.0.54(vue@3.5.11(typescript@5.6.2))(zod@3.23.8) - '@opentelemetry/api': 1.9.0 - eventsource-parser: 1.1.2 - json-schema: 0.4.0 - jsondiffpatch: 0.6.0 - nanoid: 3.3.6 - secure-json-parse: 2.7.0 - zod-to-json-schema: 3.23.2(zod@3.23.8) + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + agents@0.11.5(@babel/core@7.29.0)(@babel/runtime@7.28.6)(@cloudflare/workers-types@4.20260424.1)(ai@6.0.67(zod@4.3.6))(react@19.2.5)(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(vite@5.4.21(@types/node@20.19.30))(zod@4.3.6): + dependencies: + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@cfworker/json-schema': 4.1.1 + '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.0)(@babel/runtime@7.28.6)(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(vite@5.4.21(@types/node@20.19.30)) + ai: 6.0.67(zod@4.3.6) + cron-schedule: 6.0.0 + mimetext: 3.0.28 + nanoid: 5.1.9 + partyserver: 0.4.1(@cloudflare/workers-types@4.20260424.1) + partysocket: 1.1.18(react@19.2.5) + react: 19.2.5 + yargs: 18.0.0 + zod: 4.3.6 optionalDependencies: - openai: 4.67.1(zod@3.23.8) - react: 18.3.1 - sswr: 2.1.0(svelte@4.2.19) - svelte: 4.2.19 - zod: 3.23.8 + vite: 5.4.21(@types/node@20.19.30) transitivePeerDependencies: - - solid-js - - vue - - ansi-colors@4.1.3: {} + - '@babel/core' + - '@babel/plugin-transform-runtime' + - '@babel/runtime' + - '@cloudflare/workers-types' + - rolldown + - supports-color - ansi-regex@5.0.1: {} + ai@6.0.67(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 3.0.32(zod@4.3.6) + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 - ansi-regex@6.1.0: {} + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 - ansi-styles@4.3.0: + ajv@8.20.0: dependencies: - color-convert: 2.0.1 + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 - ansi-styles@5.2.0: {} + ansi-colors@4.1.3: {} - ansi-styles@6.2.1: {} + ansi-regex@5.0.1: {} - any-promise@1.3.0: {} + ansi-regex@6.2.2: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 + ansi-styles@6.2.3: {} - arg@4.1.3: {} + ansis@4.2.0: {} argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - aria-query@5.3.2: {} + argparse@2.0.1: {} array-union@2.1.0: {} assertion-error@2.0.1: {} - asynckit@0.4.0: {} - - axios@1.7.7: + ast-kit@3.0.0-beta.1: dependencies: - follow-redirects: 1.15.9 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - axobject-query@4.1.0: {} - - balanced-match@1.0.2: {} + '@babel/parser': 8.0.0-rc.3 + estree-walker: 3.0.3 + pathe: 2.0.3 - base64-js@1.5.1: {} + baseline-browser-mapping@2.10.21: {} better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - binary-extensions@2.3.0: {} + birpc@4.0.0: {} - binary-search@1.3.6: {} - - brace-expansion@2.0.1: + body-parser@2.2.2: dependencies: - balanced-match: 1.0.2 + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color braces@3.0.3: dependencies: fill-range: 7.1.1 - bundle-require@5.0.0(esbuild@0.23.1): + browserslist@4.28.2: dependencies: - esbuild: 0.23.1 - load-tsconfig: 0.2.5 + baseline-browser-mapping: 2.10.21 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) - cac@6.7.14: {} + bytes@3.1.2: {} - camelcase@4.1.0: {} + cac@6.7.14: {} - camelcase@6.3.0: {} + cac@7.0.0: {} - chai@5.1.1: + call-bind-apply-helpers@1.0.2: dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.1 - pathval: 2.0.0 - - chalk@5.3.0: {} + es-errors: 1.3.0 + function-bind: 1.1.2 - chardet@0.7.0: {} + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 - check-error@2.1.1: {} + caniuse-lite@1.0.30001790: {} - chokidar@3.6.0: + chai@5.3.3: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 - ci-info@3.9.0: {} + chardet@2.1.1: {} - client-only@0.0.1: {} + check-error@2.1.3: {} - code-red@1.0.4: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.12.1 - estree-walker: 3.0.3 - periscopic: 3.1.0 + ci-info@3.9.0: {} - color-convert@2.0.1: + cliui@9.0.1: dependencies: - color-name: 1.1.4 + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 - color-name@1.1.4: {} + content-disposition@1.1.0: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 + content-type@1.0.5: {} - commander@10.0.1: {} + convert-source-map@2.0.0: {} - commander@4.1.1: {} + cookie-signature@1.2.2: {} - consola@3.2.3: {} + cookie@0.7.2: {} - create-require@1.1.1: {} + core-js-pure@3.49.0: {} - cross-spawn@5.1.0: + cors@2.8.6: dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 + object-assign: 4.1.1 + vary: 1.1.2 - cross-spawn@7.0.3: + cron-schedule@6.0.0: {} + + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - css-tree@2.3.1: - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - - csstype@3.1.3: {} - dataloader@1.4.0: {} - debug@4.3.7: + debug@4.4.3: dependencies: ms: 2.1.3 - decamelize@1.2.0: {} - deep-eql@5.0.2: {} - delayed-stream@1.0.0: {} + defu@6.1.4: {} - detect-indent@6.1.0: {} - - diff-match-patch@1.0.5: {} + depd@2.0.0: {} - diff@4.0.2: {} + detect-indent@6.1.0: {} dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dotenv@16.4.5: {} + dotenv@16.6.1: {} dotenv@8.6.0: {} - eastasianwidth@0.2.0: {} + dts-resolver@2.1.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.344: {} + + emoji-regex@10.6.0: {} - emoji-regex@8.0.0: {} + empathic@2.0.0: {} - emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 - entities@4.5.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 esbuild@0.21.5: optionalDependencies: @@ -3201,70 +3239,100 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.1: + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} esprima@4.0.1: {} - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.6 - - event-target-shim@5.0.1: {} - - eventemitter3@4.0.7: {} - - eventsource-parser@1.1.2: {} - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - expr-eval@2.0.2: {} + '@types/estree': 1.0.8 + + etag@1.8.1: {} + + event-target-polyfill@0.0.4: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + expect-type@1.3.0: {} + + express-rate-limit@8.4.0(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color extendable-error@0.1.7: {} - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 + fast-deep-equal@3.1.3: {} - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -3272,44 +3340,39 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fastq@1.17.1: + fast-uri@3.1.0: {} + + fastq@1.20.1: dependencies: - reusify: 1.0.4 + reusify: 1.1.0 - fdir@6.4.0(picomatch@4.0.2): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.3 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - flat@5.0.2: {} - - follow-redirects@1.15.9: {} - - foreground-child@3.3.0: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - form-data-encoder@1.7.2: {} + forwarded@0.2.0: {} - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - formdata-node@4.4.1: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 4.0.0-beta.3 + fresh@2.0.0: {} fs-extra@7.0.1: dependencies: @@ -3326,61 +3389,88 @@ snapshots: fsevents@2.3.3: optional: true - get-func-name@2.0.2: {} + function-bind@1.1.2: {} - get-stream@6.0.1: {} + gensync@1.0.0-beta.2: {} - glob-parent@5.1.2: + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + get-intrinsic@1.3.0: dependencies: - is-glob: 4.0.3 + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 - glob@10.4.5: + get-proto@1.0.1: dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} - human-id@1.0.2: {} + has-symbols@1.1.0: {} + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + hono@4.12.15: {} - human-signals@2.1.0: {} + hookable@6.1.0: {} - humanize-ms@1.2.1: + http-errors@2.0.1: dependencies: - ms: 2.1.3 + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + human-id@4.1.3: {} - iconv-lite@0.4.24: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 ignore@5.3.2: {} - infobox-parser@3.6.4: - dependencies: - camelcase: 4.1.0 + import-without-cache@0.2.5: {} - is-any-array@2.0.1: {} + inherits@2.0.4: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 + ip-address@10.1.0: {} - is-extglob@2.1.1: {} + ipaddr.js@1.9.1: {} - is-fullwidth-code-point@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: @@ -3388,11 +3478,7 @@ snapshots: is-number@7.0.0: {} - is-reference@3.0.2: - dependencies: - '@types/estree': 1.0.6 - - is-stream@2.0.1: {} + is-promise@4.0.0: {} is-subdir@1.2.0: dependencies: @@ -3402,93 +3488,56 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - joycon@3.1.1: {} + jose@6.2.2: {} - js-tiktoken@1.0.15: - dependencies: - base64-js: 1.5.1 + js-base64@3.7.8: {} js-tokens@4.0.0: {} - js-yaml@3.14.1: + js-yaml@3.14.2: dependencies: argparse: 1.0.10 esprima: 4.0.1 - json-schema-to-ts@3.1.1: + js-yaml@4.1.1: dependencies: - '@babel/runtime': 7.25.7 - ts-algebra: 2.0.0 + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} json-schema@0.4.0: {} - jsondiffpatch@0.6.0: - dependencies: - '@types/diff-match-patch': 1.0.36 - chalk: 5.3.0 - diff-match-patch: 1.0.5 + json5@2.2.3: {} jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 - langsmith@0.1.61(openai@4.67.1(zod@3.23.8)): - dependencies: - '@types/uuid': 10.0.0 - commander: 10.0.1 - p-queue: 6.6.2 - p-retry: 4.6.2 - semver: 7.6.3 - uuid: 10.0.0 - optionalDependencies: - openai: 4.67.1(zod@3.23.8) - - lilconfig@3.1.2: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - - locate-character@3.0.0: {} - locate-path@5.0.0: dependencies: p-locate: 4.1.0 - lodash.sortby@4.7.0: {} - lodash.startcase@4.4.0: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - loupe@3.1.1: - dependencies: - get-func-name: 2.0.2 - - lru-cache@10.4.3: {} + loupe@3.2.1: {} - lru-cache@4.1.5: + lru-cache@5.1.1: dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 + yallist: 3.1.1 - magic-string@0.30.11: + magic-string@0.30.21: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 - make-error@1.3.6: {} + math-intrinsics@1.1.0: {} - mdn-data@2.0.30: {} + media-typer@1.1.0: {} - merge-stream@2.0.0: {} + merge-descriptors@2.0.0: {} merge2@1.4.1: {} @@ -3499,92 +3548,52 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 - mimic-fn@2.1.0: {} - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.1 - - minipass@7.1.2: {} - - ml-array-mean@1.1.6: - dependencies: - ml-array-sum: 1.1.6 - - ml-array-sum@1.1.6: - dependencies: - is-any-array: 2.0.1 - - ml-distance-euclidean@2.0.0: {} - - ml-distance@4.0.1: + mime-types@3.0.2: dependencies: - ml-array-mean: 1.1.6 - ml-distance-euclidean: 2.0.0 - ml-tree-similarity: 1.0.0 + mime-db: 1.54.0 - ml-tree-similarity@1.0.0: + mimetext@3.0.28: dependencies: - binary-search: 1.3.6 - num-sort: 2.1.0 + '@babel/runtime': 7.28.6 + '@babel/runtime-corejs3': 7.29.2 + js-base64: 3.7.8 + mime-types: 2.1.35 mri@1.2.0: {} ms@2.1.3: {} - mustache@4.2.0: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.6: {} + nanoid@3.3.11: {} - nanoid@3.3.7: {} + nanoid@5.1.9: {} - node-domexception@1.0.0: {} + negotiator@1.0.0: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - num-sort@2.1.0: {} + node-releases@2.0.38: {} object-assign@4.1.1: {} - object-hash@3.0.0: {} + object-inspect@1.13.4: {} - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 + obug@2.1.1: {} - openai@4.67.1(zod@3.23.8): + on-finished@2.4.1: dependencies: - '@types/node': 18.19.54 - '@types/node-fetch': 2.6.11 - abort-controller: 3.0.0 - agentkeepalive: 4.5.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - optionalDependencies: - zod: 3.23.8 - transitivePeerDependencies: - - encoding + ee-first: 1.1.1 - os-tmpdir@1.0.2: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 outdent@0.5.0: {} @@ -3592,8 +3601,6 @@ snapshots: dependencies: p-map: 2.1.0 - p-finally@1.0.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -3604,149 +3611,257 @@ snapshots: p-map@2.1.0: {} - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 + p-try@2.2.0: {} - p-timeout@3.2.0: + package-manager-detector@0.2.11: dependencies: - p-finally: 1.0.0 + quansync: 0.2.11 - p-try@2.2.0: {} + parseurl@1.3.3: {} - package-json-from-dist@1.0.1: {} + partyserver@0.4.1(@cloudflare/workers-types@4.20260424.1): + dependencies: + '@cloudflare/workers-types': 4.20260424.1 + nanoid: 5.1.9 - package-manager-detector@0.2.1: {} + partysocket@1.1.18(react@19.2.5): + dependencies: + event-target-polyfill: 0.0.4 + optionalDependencies: + react: 19.2.5 path-exists@4.0.0: {} path-key@3.1.1: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 + path-to-regexp@8.4.2: {} path-type@4.0.0: {} pathe@1.1.2: {} - pathval@2.0.0: {} + pathe@2.0.3: {} - periscopic@3.1.0: - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.2 + pathval@2.0.1: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} - picomatch@4.0.2: {} + picomatch@4.0.3: {} - pify@4.0.1: {} + picomatch@4.0.4: {} - pirates@4.0.6: {} + pify@4.0.1: {} - postcss-load-config@6.0.1(postcss@8.4.47): - dependencies: - lilconfig: 3.1.2 - optionalDependencies: - postcss: 8.4.47 + pkce-challenge@5.0.1: {} - postcss@8.4.47: + postcss@8.5.6: dependencies: - nanoid: 3.3.7 - picocolors: 1.1.0 + nanoid: 3.3.11 + picocolors: 1.1.1 source-map-js: 1.2.1 prettier@2.8.8: {} - proxy-from-env@1.1.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 - pseudomap@1.0.2: {} + quansync@0.2.11: {} - punycode@2.3.1: {} + quansync@1.0.0: {} queue-microtask@1.2.3: {} - react@18.3.1: + range-parser@1.2.1: {} + + raw-body@3.0.2: dependencies: - loose-envify: 1.4.0 + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + react@19.2.5: {} read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.14.1 + js-yaml: 3.14.2 pify: 4.0.1 strip-bom: 3.0.0 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - regenerator-runtime@0.14.1: {} + require-from-string@2.0.2: {} resolve-from@5.0.0: {} - retry@0.13.1: {} + resolve-pkg-maps@1.0.0: {} - reusify@1.0.4: {} + reusify@1.1.0: {} + + rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(typescript@5.9.3): + dependencies: + '@babel/generator': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 2.1.3 + get-tsconfig: 4.13.7 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' - rollup@4.24.0: + rollup@4.57.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 safer-buffer@2.1.2: {} - secure-json-parse@2.7.0: {} + semver@6.3.1: {} - semver@7.6.3: {} + semver@7.7.3: {} + + semver@7.7.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color - shebang-command@1.2.0: + serve-static@2.2.1: dependencies: - shebang-regex: 1.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} - siginfo@2.0.0: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 - signal-exit@3.0.7: {} + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3754,205 +3869,144 @@ snapshots: source-map-js@1.2.1: {} - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - - spawndamnit@2.0.0: + spawndamnit@3.0.1: dependencies: - cross-spawn: 5.1.0 - signal-exit: 3.0.7 + cross-spawn: 7.0.6 + signal-exit: 4.1.0 sprintf-js@1.0.3: {} - sswr@2.1.0(svelte@4.2.19): - dependencies: - svelte: 4.2.19 - swrev: 4.0.0 - stackback@0.0.2: {} - std-env@3.7.0: {} + statuses@2.0.2: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + std-env@3.10.0: {} - string-width@5.1.2: + string-width@7.2.0: dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.2.0: dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} - strip-final-newline@2.0.0: {} - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - - svelte@4.2.19: - dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - '@types/estree': 1.0.6 - acorn: 8.12.1 - aria-query: 5.3.2 - axobject-query: 4.1.0 - code-red: 1.0.4 - css-tree: 2.3.1 - estree-walker: 3.0.3 - is-reference: 3.0.2 - locate-character: 3.0.0 - magic-string: 0.30.11 - periscopic: 3.1.0 - - swr@2.2.5(react@18.3.1): - dependencies: - client-only: 0.0.1 - react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) - - swrev@4.0.0: {} - - swrv@1.0.4(vue@3.5.11(typescript@5.6.2)): - dependencies: - vue: 3.5.11(typescript@5.6.2) - term-size@2.2.1: {} - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - tinybench@2.9.0: {} - tinyexec@0.3.0: {} + tinyexec@0.3.2: {} + + tinyexec@1.0.4: {} - tinyglobby@0.2.9: + tinyglobby@0.2.15: dependencies: - fdir: 6.4.0(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - tinypool@1.0.1: {} + tinypool@1.1.1: {} tinyrainbow@1.2.0: {} tinyspy@3.0.2: {} - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - tr46@0.0.3: {} + toidentifier@1.0.1: {} - tr46@1.0.1: - dependencies: - punycode: 2.3.1 + tr46@0.0.3: {} tree-kill@1.2.2: {} - ts-algebra@2.0.0: {} - - ts-interface-checker@0.1.13: {} + tsdown@0.21.7(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(typescript@5.9.3): + dependencies: + ansis: 4.2.0 + cac: 7.0.0 + defu: 6.1.4 + empathic: 2.0.0 + hookable: 6.1.0 + import-without-cache: 0.2.5 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + rolldown-plugin-dts: 0.23.2(rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1))(typescript@5.9.3) + semver: 7.7.4 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + unrun: 0.2.34(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - synckit + - vue-tsc + + tslib@2.8.1: + optional: true - ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2): + tsx@4.21.0: dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.10 - acorn: 8.12.1 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.6.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + esbuild: 0.27.7 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 - tsup@8.3.0(postcss@8.4.47)(typescript@5.6.2): + type-is@2.0.1: dependencies: - bundle-require: 5.0.0(esbuild@0.23.1) - cac: 6.7.14 - chokidar: 3.6.0 - consola: 3.2.3 - debug: 4.3.7 - esbuild: 0.23.1 - execa: 5.1.1 - joycon: 3.1.1 - picocolors: 1.1.0 - postcss-load-config: 6.0.1(postcss@8.4.47) - resolve-from: 5.0.0 - rollup: 4.24.0 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tinyglobby: 0.2.9 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.47 - typescript: 5.6.2 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 - typescript@5.6.2: {} + typescript@5.9.3: {} - undici-types@5.26.5: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 - undici-types@6.19.8: {} + undici-types@6.21.0: {} universalify@0.1.2: {} - use-sync-external-store@1.2.2(react@18.3.1): - dependencies: - react: 18.3.1 + unpipe@1.0.0: {} - uuid@10.0.0: {} + unrun@0.2.34(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): + dependencies: + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' - uuid@9.0.1: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 - v8-compile-cache-lib@3.0.1: {} + vary@1.1.2: {} - vite-node@2.1.2(@types/node@20.16.10): + vite-node@2.1.9(@types/node@20.19.30): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.3 + es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.8(@types/node@20.16.10) + vite: 5.4.21(@types/node@20.19.30) transitivePeerDependencies: - '@types/node' - less @@ -3964,38 +4018,39 @@ snapshots: - supports-color - terser - vite@5.4.8(@types/node@20.16.10): + vite@5.4.21(@types/node@20.19.30): dependencies: esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.24.0 + postcss: 8.5.6 + rollup: 4.57.1 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.19.30 fsevents: 2.3.3 - vitest@2.1.2(@types/node@20.16.10): - dependencies: - '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.8(@types/node@20.16.10)) - '@vitest/pretty-format': 2.1.2 - '@vitest/runner': 2.1.2 - '@vitest/snapshot': 2.1.2 - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 - chai: 5.1.1 - debug: 4.3.7 - magic-string: 0.30.11 + vitest@2.1.9(@types/node@20.19.30): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.30)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 pathe: 1.1.2 - std-env: 3.7.0 + std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.0 - tinypool: 1.0.1 + tinyexec: 0.3.2 + tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.8(@types/node@20.16.10) - vite-node: 2.1.2(@types/node@20.16.10) + vite: 5.4.21(@types/node@20.19.30) + vite-node: 2.1.9(@types/node@20.19.30) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.19.30 transitivePeerDependencies: - less - lightningcss @@ -4007,37 +4062,13 @@ snapshots: - supports-color - terser - vue@3.5.11(typescript@5.6.2): - dependencies: - '@vue/compiler-dom': 3.5.11 - '@vue/compiler-sfc': 3.5.11 - '@vue/runtime-dom': 3.5.11 - '@vue/server-renderer': 3.5.11(vue@3.5.11(typescript@5.6.2)) - '@vue/shared': 3.5.11 - optionalDependencies: - typescript: 5.6.2 - - web-streams-polyfill@4.0.0-beta.3: {} - webidl-conversions@3.0.1: {} - webidl-conversions@4.0.2: {} - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -4047,37 +4078,33 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wikipedia@2.1.2: + wrap-ansi@9.0.2: dependencies: - axios: 1.7.7 - infobox-parser: 3.6.4 - transitivePeerDependencies: - - debug + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + wrappy@1.0.2: {} - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 + xstate@5.26.0: {} - xstate@5.18.2: {} + y18n@5.0.8: {} - yallist@2.1.2: {} + yallist@3.1.1: {} - yn@3.1.1: {} + yargs-parser@22.0.0: {} - zod-to-json-schema@3.23.2(zod@3.23.8): + yargs@18.0.0: dependencies: - zod: 3.23.8 + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 - zod-to-json-schema@3.23.3(zod@3.23.8): + zod-to-json-schema@3.25.2(zod@4.3.6): dependencies: - zod: 3.23.8 + zod: 4.3.6 - zod@3.23.8: {} + zod@4.3.6: {} diff --git a/readme.md b/readme.md index dbca6f1..8a9acb4 100644 --- a/readme.md +++ b/readme.md @@ -7,4 +7,61 @@ Stately Agent is a flexible framework for building AI agents using state machine - Enabling custom **planning** abilities for agents to achieve specific goals based on state machine logic, observations, and feedback - First-class integration with the [Vercel AI SDK](https://sdk.vercel.ai/) to easily support multiple model providers, such as OpenAI, Anthropic, Google, Mistral, Groq, Perplexity, and more +## Examples + + + +The examples in [`examples/`](/Users/davidkpiano/Code/agent/examples) are intentionally small. Most run in the CLI and use real OpenAI calls when `OPENAI_API_KEY` is set. Runtime-specific examples call out extra environment requirements inline. + +If you want examples grouped by intent instead of a flat list, start with [`examples/README.md`](/Users/davidkpiano/Code/agent/examples/README.md). It separates app-shaped examples, workflow examples, runtime integrations, and lower-level reference examples. + +Run them with `node --import tsx examples/.ts`. + +Convert a machine file to diagram output with `pnpm agent:convert --format mermaid` or `pnpm agent:convert --format xstate`. Static analysis warnings are printed to stderr. For programmatic access, use `analyzeGraph(...)` from `@statelyai/agent/graph`; warnings are returned explicitly instead of being hidden in graph metadata. + +Start here: + +- App-shaped integrations: [`examples/apps/next/`](/Users/davidkpiano/Code/agent/examples/apps/next), [`examples/apps/cloudflare-agents/`](/Users/davidkpiano/Code/agent/examples/apps/cloudflare-agents), [`examples/next-ai-sdk-ui.ts`](/Users/davidkpiano/Code/agent/examples/next-ai-sdk-ui.ts), [`examples/cloudflare-agents.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-agents.ts) +- Durable sessions and transports: [`examples/persistence.ts`](/Users/davidkpiano/Code/agent/examples/persistence.ts), [`examples/http-session.ts`](/Users/davidkpiano/Code/agent/examples/http-session.ts), [`examples/http-streaming-session.ts`](/Users/davidkpiano/Code/agent/examples/http-streaming-session.ts) +- Core workflow patterns: [`examples/rag.ts`](/Users/davidkpiano/Code/agent/examples/rag.ts), [`examples/tool-calling.ts`](/Users/davidkpiano/Code/agent/examples/tool-calling.ts), [`examples/error-retry.ts`](/Users/davidkpiano/Code/agent/examples/error-retry.ts), [`examples/spec-agent-loop.ts`](/Users/davidkpiano/Code/agent/examples/spec-agent-loop.ts), [`examples/persistent-supervisor.ts`](/Users/davidkpiano/Code/agent/examples/persistent-supervisor.ts) +- CrewAI-style equivalents: [`examples/content-creator-flow.ts`](/Users/davidkpiano/Code/agent/examples/content-creator-flow.ts), [`examples/email-auto-responder-flow.ts`](/Users/davidkpiano/Code/agent/examples/email-auto-responder-flow.ts), [`examples/lead-score-flow.ts`](/Users/davidkpiano/Code/agent/examples/lead-score-flow.ts), [`examples/meeting-assistant-flow.ts`](/Users/davidkpiano/Code/agent/examples/meeting-assistant-flow.ts), [`examples/self-evaluation-loop-flow.ts`](/Users/davidkpiano/Code/agent/examples/self-evaluation-loop-flow.ts), [`examples/write-a-book-flow.ts`](/Users/davidkpiano/Code/agent/examples/write-a-book-flow.ts) +- Reference examples: [`examples/simple.ts`](/Users/davidkpiano/Code/agent/examples/simple.ts), [`examples/decide.ts`](/Users/davidkpiano/Code/agent/examples/decide.ts), [`examples/classify.ts`](/Users/davidkpiano/Code/agent/examples/classify.ts), [`examples/adapter.ts`](/Users/davidkpiano/Code/agent/examples/adapter.ts), [`examples/workflow-guardrails.ts`](/Users/davidkpiano/Code/agent/examples/workflow-guardrails.ts) + +Use `classify(...)` when the result is just "what kind of thing is this?" Use `decide(...)` when the result is "what should happen next?" and the chosen branch may need structured data. + +CrewAI Flow parity is tracked in [`docs/crewai-parity.md`](/Users/davidkpiano/Code/agent/docs/crewai-parity.md), the same way LangGraph parity is tracked separately. + +## Runtime Adapters + + + +The core package exports session helpers from `@statelyai/agent` and `@statelyai/agent/runtime`: + +- `waitForRunDone(run)`: await terminal success or reject on session error +- `waitForRunSnapshot(run, predicate)`: await the next snapshot that matches a predicate + +Use the framework adapters when a machine needs to run inside an app runtime: + +- `@statelyai/agent/http`: `createSessionHttpController(...)`, `createSessionHttpHandler(...)`, and `createRunSseResponse(...)` +- `@statelyai/agent/next`: `createNextSessionRouteHandlers(...)` plus App Router config exports +- `@statelyai/agent/cloudflare`: `createDurableObjectRunStore(...)` and `createCloudflareAgentRunStore(...)` + +## Persistence Adapters + + + +Storage adapters are intentionally bring-your-own. Implement the `RunStore` contract with four methods: + +- `append(sessionId, event)` +- `loadEvents(sessionId, afterSequence?)` +- `loadLatestSnapshot(sessionId)` +- `saveSnapshot(snapshot)` + +Use these examples as templates for your storage layer: + +- [`examples/persistence.ts`](/Users/davidkpiano/Code/agent/examples/persistence.ts): the smallest durable session flow with an in-memory store +- [`examples/http-session.ts`](/Users/davidkpiano/Code/agent/examples/http-session.ts): the Request/Response transport shape around `@statelyai/agent/http` +- [`examples/cloudflare-durable-object.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-durable-object.ts): Durable Object-backed event and snapshot persistence with `@statelyai/agent/cloudflare` +- [`examples/cloudflare-agents.ts`](/Users/davidkpiano/Code/agent/examples/cloudflare-agents.ts): syncing a `RunStore` into Cloudflare Agents state with `@statelyai/agent/cloudflare` + **Read the documentation: [stately.ai/docs/agents](https://stately.ai/docs/agents)** diff --git a/scripts/agent-convert.ts b/scripts/agent-convert.ts new file mode 100644 index 0000000..4142c29 --- /dev/null +++ b/scripts/agent-convert.ts @@ -0,0 +1,227 @@ +import { writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { analyzeGraph, toMermaid, type AgentGraphWarning } from '../src/graph/index.js'; +import type { AgentMachine } from '../src/index.js'; +import { toXStateVisualization } from '../src/xstate/index.js'; + +type Format = 'mermaid' | 'xstate'; + +interface CliOptions { + file?: string; + format: Format; + exportName?: string; + factoryName?: string; + outFile?: string; + help: boolean; +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + + if (options.help || !options.file) { + printHelp(); + process.exit(options.help ? 0 : 1); + } + + const machine = await loadMachine(options); + const analysis = analyzeGraph(machine); + writeWarnings(analysis.warnings); + + const output = + options.format === 'mermaid' + ? toMermaid(machine) + : `${JSON.stringify(toXStateVisualization(machine), null, 2)}\n`; + + if (options.outFile) { + await writeFile(resolve(options.outFile), output); + return; + } + + process.stdout.write(output.endsWith('\n') ? output : `${output}\n`); +} + +function writeWarnings(warnings: AgentGraphWarning[]) { + for (const warning of warnings) { + process.stderr.write( + [ + '[agent:convert]', + `${warning.state} on ${warning.event}:`, + warning.message, + ].join(' ') + '\n' + ); + } +} + +function parseArgs(args: string[]): CliOptions { + const options: CliOptions = { + format: 'mermaid', + help: false, + }; + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]!; + + if (arg === '--help' || arg === '-h') { + options.help = true; + continue; + } + + if (arg === '--format' || arg === '-f') { + options.format = parseFormat(requiredValue(args, index, arg)); + index += 1; + continue; + } + + if (arg.startsWith('--format=')) { + options.format = parseFormat(arg.slice('--format='.length)); + continue; + } + + if (arg === '--export' || arg === '-e') { + options.exportName = requiredValue(args, index, arg); + index += 1; + continue; + } + + if (arg.startsWith('--export=')) { + options.exportName = arg.slice('--export='.length); + continue; + } + + if (arg === '--factory') { + options.factoryName = requiredValue(args, index, arg); + index += 1; + continue; + } + + if (arg.startsWith('--factory=')) { + options.factoryName = arg.slice('--factory='.length); + continue; + } + + if (arg === '--out' || arg === '-o') { + options.outFile = requiredValue(args, index, arg); + index += 1; + continue; + } + + if (arg.startsWith('--out=')) { + options.outFile = arg.slice('--out='.length); + continue; + } + + if (arg.startsWith('-')) { + throw new Error(`Unknown option: ${arg}`); + } + + if (options.file) { + throw new Error(`Unexpected positional argument: ${arg}`); + } + + options.file = arg; + } + + return options; +} + +function parseFormat(value: string): Format { + if (value === 'mermaid' || value === 'xstate') { + return value; + } + + throw new Error(`Unsupported format '${value}'. Use 'mermaid' or 'xstate'.`); +} + +function requiredValue(args: string[], index: number, option: string): string { + const value = args[index + 1]; + if (!value || value.startsWith('-')) { + throw new Error(`Missing value for ${option}`); + } + + return value; +} + +async function loadMachine(options: CliOptions): Promise { + const fileUrl = pathToFileURL(resolve(options.file!)).href; + const mod = await import(fileUrl) as Record; + + if (options.factoryName) { + const factory = mod[options.factoryName]; + if (typeof factory !== 'function') { + throw new Error(`Export '${options.factoryName}' is not a function.`); + } + + const machine = await factory(); + return assertAgentMachine(machine, `factory '${options.factoryName}'`); + } + + if (options.exportName) { + return assertAgentMachine( + mod[options.exportName], + `export '${options.exportName}'` + ); + } + + for (const candidate of [mod.default, mod.machine]) { + if (isAgentMachine(candidate)) { + return candidate; + } + } + + const namedMachines = Object.entries(mod).filter(([, value]) => + isAgentMachine(value) + ); + if (namedMachines.length === 1) { + return namedMachines[0]![1] as AgentMachine; + } + + throw new Error( + [ + 'Could not find an agent machine export.', + 'Export a machine as default or named `machine`, or pass `--export `.', + 'For zero-arg factory exports, pass `--factory `.', + ].join(' ') + ); +} + +function assertAgentMachine(value: unknown, label: string): AgentMachine { + if (!isAgentMachine(value)) { + throw new Error(`${label} did not return an agent machine.`); + } + + return value; +} + +function isAgentMachine(value: unknown): value is AgentMachine { + return ( + !!value + && typeof value === 'object' + && typeof (value as AgentMachine).id === 'string' + && typeof (value as AgentMachine).getInitialState === 'function' + && typeof (value as AgentMachine).transition === 'function' + && typeof (value as AgentMachine).execute === 'function' + ); +} + +function printHelp() { + process.stdout.write(`Usage: + pnpm agent:convert [--format mermaid|xstate] + +Options: + -f, --format Output format. Defaults to mermaid. + -e, --export Named export containing an agent machine. + --factory Named zero-arg factory that returns an agent machine. + -o, --out Write output to a file instead of stdout. + -h, --help Show this help. + +Examples: + pnpm agent:convert ./examples/simple.ts --factory createSimpleExample + pnpm agent:convert ./examples/simple.ts --factory createSimpleExample --format xstate +`); +} + +main().catch((error) => { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exit(1); +}); diff --git a/src/adapter.ts b/src/adapter.ts new file mode 100644 index 0000000..5edb2a3 --- /dev/null +++ b/src/adapter.ts @@ -0,0 +1,8 @@ +import type { AgentAdapter } from './types.js'; + +/** + * Create a custom adapter for model execution. + */ +export function createAdapter(impl: AgentAdapter): AgentAdapter { + return impl; +} diff --git a/src/agent-convert-cli.test.ts b/src/agent-convert-cli.test.ts new file mode 100644 index 0000000..a048738 --- /dev/null +++ b/src/agent-convert-cli.test.ts @@ -0,0 +1,84 @@ +import { execFile } from 'node:child_process'; +import { mkdtemp, readFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { promisify } from 'node:util'; +import { expect, test } from 'vitest'; + +const execFileAsync = promisify(execFile); + +test('agent:convert writes Mermaid and XState output from machine files', async () => { + const tmp = await mkdtemp(join(tmpdir(), 'agent-convert-')); + const fixture = resolve('src/fixtures/converter-machine.ts'); + + const mermaidFile = join(tmp, 'default.mmd'); + await runConvert([fixture, '--format', 'mermaid', '--out', mermaidFile]); + await expect(readFile(mermaidFile, 'utf8')).resolves.toBe(`stateDiagram-v2 + [*] --> idle + idle --> done : submit [event.ok] + idle --> rejected : submit [!(event.ok)] + rejected --> [*] + done --> [*]`); + + const namedXStateFile = join(tmp, 'named.json'); + await runConvert([ + fixture, + '--export', + 'namedMachine', + '--format', + 'xstate', + '--out', + namedXStateFile, + ]); + const namedXState = JSON.parse(await readFile(namedXStateFile, 'utf8')) as { + id: string; + initial: string; + states: Record; + }; + expect(namedXState.id).toBe('named-converter-machine'); + expect(namedXState.initial).toBe('idle'); + expect(namedXState).toMatchObject({ + meta: { + agent: { + format: '@statelyai/agent/xstate-visualization', + runnable: false, + }, + }, + }); + expect(Object.keys(namedXState.states)).toEqual(['idle', 'rejected', 'done']); + + const factoryXStateFile = join(tmp, 'factory.json'); + await runConvert([ + fixture, + '--factory', + 'createFixtureMachine', + '--format', + 'xstate', + '--out', + factoryXStateFile, + ]); + const factoryXState = JSON.parse(await readFile(factoryXStateFile, 'utf8')) as { + id: string; + }; + expect(factoryXState.id).toBe('factory-converter-machine'); + + const warningFile = join(tmp, 'warning.mmd'); + const warningResult = await runConvert([ + fixture, + '--export', + 'warningMachine', + '--format', + 'mermaid', + '--out', + warningFile, + ]); + expect(warningResult.stderr).toContain( + '[agent:convert] idle on go: Unsupported helper call: unknownTransition() is not statically resolvable.' + ); +}, 20000); + +async function runConvert(args: string[]) { + return execFileAsync('pnpm', ['agent:convert', ...args], { + cwd: resolve('.'), + }); +} diff --git a/src/agent.test.ts b/src/agent.test.ts index 443f423..ec37e3e 100644 --- a/src/agent.test.ts +++ b/src/agent.test.ts @@ -1,326 +1,1846 @@ -import { test, expect, vi } from 'vitest'; -import { createAgent } from './'; -import { createActor, createMachine } from 'xstate'; -import { LanguageModelV1CallOptions } from 'ai'; +import { describe, expect, test, vi } from 'vitest'; import { z } from 'zod'; -import { dummyResponseValues, MockLanguageModelV1 } from './mockModel'; +import { + classify, + classifyResultSchema, + createAgentMachine, + createAdapter, + decide, + decideResultSchema, +} from './index.js'; +import type { DecideAdapter } from './types.js'; -test('an agent has the expected interface', () => { - const agent = createAgent({ - name: 'test', - events: {}, - model: new MockLanguageModelV1(), +// ─── Test helpers ─── + +function mockAdapter( + responses: Array<{ + choice: string; + data?: Record; + reasoning?: string; + }> +): DecideAdapter { + let index = 0; + return { + decide: async () => { + const response = responses[index++]; + if (!response) throw new Error('No more mock responses'); + return { + choice: response.choice, + data: response.data ?? {}, + reasoning: response.reasoning, + }; + }, + }; +} + +const choiceResultSchema = z.object({ + choice: z.string(), + data: z.record(z.string(), z.unknown()), + reasoning: z.string().optional(), +}); + +// ─── Simple machine (no schemas — inferred from context) ─── + +function createSimpleMachine() { + return createAgentMachine({ + id: 'simple', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + start: ({ target: 'running' }), + }, + }, + running: { + schemas: { output: z.object({ value: z.number() }) }, + invoke: async ({ context }) => { + // context.count is typed as number ✓ + return { value: context.count + 1 }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { count: output.value }, + }), + }, + done: { + type: 'final', + // is the machine output inferred? should we have top-level outputSchema? + output: ({ context }) => ({ result: context.count }), + }, + }, + }); +} + +// ─── HITL machine (with schemas) ─── + +function createHitlMachine() { + return createAgentMachine({ + id: 'hitl', + schemas: { + input: z.object({ task: z.string() }), + events: { + 'user.message': z.object({ message: z.string() }), + 'user.approve': z.object({}), + 'user.cancel': z.object({}), + }, + }, + context: (input) => ({ + task: input.task, + messages: [] as Array<{ role: string; content: string }>, + result: null as string | null, + }), + initial: 'gathering', + states: { + gathering: { + on: { + // events are now typed from schemas.events + 'user.message': ({ event, context }) => ({ + context: { + messages: [ + ...context.messages, + { role: 'user', content: event.message }, + ], + }, + }), + // static shorthand — string target + 'user.approve': { target: 'processing' }, + 'user.cancel': { target: 'cancelled' }, + }, + }, + processing: { + schemas: { output: z.object({ output: z.string() }) }, + invoke: async ({ context }) => { + // context.messages is typed ✓ + return { + output: `Processed: ${context.messages.map((m) => m.content).join(', ')}`, + }; + }, + onDone: ({ output }) => ({ + target: 'reviewing', + context: { result: output.output }, + }), + }, + reviewing: { + on: { + // static shorthand — object target + 'user.approve': { target: 'done' }, + 'user.message': ({ event, context }) => ({ + target: 'processing', + context: { + messages: [ + ...context.messages, + { role: 'user', content: event.message }, + ], + }, + }), + 'user.cancel': { target: 'cancelled' }, + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ result: context.result }), + }, + cancelled: { + type: 'final', + output: () => ({ cancelled: true }), + }, + }, + }); +} + +// ─── Decide machine ─── + +function createDecideMachine(adapter: DecideAdapter) { + const options = { + billing: { description: 'Billing issues' }, + technical: { description: 'Technical issues' }, + general: { description: 'General inquiries' }, + } as const; + + return createAgentMachine({ + id: 'decider', + context: () => ({ + issue: 'App crashes on login', + category: null as string | null, + resolution: null as string | null, + }), + initial: 'classifying', + states: { + classifying: { + schemas: { output: decideResultSchema(options) }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'test-model', + prompt: `Classify: ${context.issue}`, + options, + }), + onDone: ({ output }) => ({ + target: 'handling', + context: { category: output.choice }, + }), + }, + handling: { + schemas: { output: z.object({ resolution: z.string() }) }, + invoke: async ({ context }) => ({ + resolution: `Handled ${context.category} issue`, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { resolution: output.resolution }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + category: context.category, + resolution: context.resolution, + }), + }, + }, + }); +} + +// ─── Classify machine ─── + +function createClassifyMachine(adapter: DecideAdapter) { + const categories = { + billing: { description: 'Billing, payments, refunds' }, + technical: { description: 'Technical issues, bugs' }, + general: { description: 'General inquiries' }, + } as const; + + return createAgentMachine({ + id: 'classifier', + context: () => ({ + issue: 'I want my money back', + category: null as string | null, + }), + initial: 'classifyIntent', + states: { + classifyIntent: { + schemas: { output: classifyResultSchema(categories) }, + invoke: async ({ context }) => + classify({ + adapter, + model: 'test-model', + prompt: `Classify: "${context.issue}"`, + into: categories, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { category: output.category }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ category: context.category }), + }, + }, }); +} + - expect(agent.decide).toBeDefined(); +// ═══════════════════════════════════════ +// Tests +// ═══════════════════════════════════════ + +describe('createAgentMachine', () => { + test('returns machine with typed methods', () => { + const machine = createSimpleMachine(); + expect(machine.id).toBe('simple'); + expect(typeof machine.getInitialState).toBe('function'); + expect(typeof machine.transition).toBe('function'); + expect(typeof machine.invoke).toBe('function'); + expect(typeof machine.execute).toBe('function'); + expect(typeof machine.stream).toBe('function'); + expect(typeof machine.resolveState).toBe('function'); + }); +}); + +describe('getInitialState', () => { + test('creates initial state (sync)', () => { + const machine = createSimpleMachine(); + const state = machine.getInitialState(); + expect(state.value).toBe('idle'); + expect(state.context).toEqual({ count: 0 }); + expect(state.status).toBe('active'); + }); + + test('validates input via schemas.input (sync)', () => { + const machine = createHitlMachine(); + const state = machine.getInitialState({ task: 'test task' }); + expect(state.context.task).toBe('test task'); + }); + + test('rejects invalid input', () => { + const machine = createHitlMachine(); + // Runtime validation catches invalid input (schemas.input validates) + const invalidInput = { task: 123 } as unknown as { task: string }; + expect(() => machine.getInitialState(invalidInput)).toThrow(); + }); - expect(agent.addMessage).toBeDefined(); - expect(agent.addObservation).toBeDefined(); - expect(agent.addFeedback).toBeDefined(); - expect(agent.addPlan).toBeDefined(); + test('resolves string initial', () => { + const machine = createSimpleMachine(); + expect(machine.getInitialState().value).toBe('idle'); + }); - expect(agent.getMessages).toBeDefined(); - expect(agent.getObservations).toBeDefined(); - expect(agent.getFeedback).toBeDefined(); - expect(agent.getPlans).toBeDefined(); + test('resolves function initial', () => { + const machine = createAgentMachine({ + id: 'fn-initial', + context: (input: string) => ({ mode: input }), + initial: ({ context }) => ({ + target: (context.mode === 'fast' ? 'fast' : 'slow') as 'fast' | 'slow', + }), + states: { + fast: { type: 'final' }, + slow: { type: 'final' }, + }, + }); + expect(machine.getInitialState('fast').value).toBe('fast'); + }); - expect(agent.interact).toBeDefined(); }); -test('agent.addMessage() adds to message history', () => { - const model = new MockLanguageModelV1(); +describe('invoke', () => { + test('executes invoke and transitions via onDone', async () => { + const machine = createSimpleMachine(); + let state = machine.getInitialState(); + state = machine.transition(state, { type: 'start' }); + state = await machine.invoke(state); + expect(state.value).toBe('done'); + expect(state.context.count).toBe(1); + }); - const agent = createAgent({ - name: 'test', - events: {}, - model, + test('returns pending for event-only states', async () => { + const machine = createHitlMachine(); + const state = await machine.invoke(machine.getInitialState({ task: 'x' })); + expect(state.status).toBe('pending'); + expect(state.value).toBe('gathering'); }); - agent.addMessage({ - role: 'user', - content: [{ type: 'text', text: 'msg 1' }], + test('returns done for final states', async () => { + const machine = createSimpleMachine(); + let s = machine.transition(machine.getInitialState(), { type: 'start' }); + s = await machine.invoke(s); + s = await machine.invoke(s); + expect(s.status).toBe('done'); + expect(s.output).toEqual({ result: 1 }); }); - const messageHistory = agent.addMessage({ - role: 'assistant', - content: [{ type: 'text', text: 'response 1' }], + test('handles decide with adapter', async () => { + const machine = createDecideMachine( + mockAdapter([{ choice: 'technical' }]) + ); + const s = await machine.invoke(machine.getInitialState()); + expect(s.value).toBe('handling'); + expect(s.context.category).toBe('technical'); }); - expect(messageHistory.sessionId).toEqual(agent.sessionId); + test('handles classify', async () => { + const machine = createClassifyMachine( + mockAdapter([{ choice: 'billing' }]) + ); + const s = await machine.invoke(machine.getInitialState()); + expect(s.value).toBe('done'); + expect(s.context.category).toBe('billing'); + }); - expect(agent.getMessages()).toContainEqual( - expect.objectContaining({ - content: [expect.objectContaining({ text: 'msg 1' })], - }) - ); + test('errors without adapter', async () => { + const machine = createAgentMachine({ + id: 'no-adapter', + context: () => ({}), + initial: 'deciding', + states: { + deciding: { + invoke: async () => + decide({ + model: 'test', + prompt: 'test', + options: { a: { description: 'A' } }, + }), + onDone: () => ({ target: 'done' }), + }, + done: { type: 'final' }, + }, + }); + const s = await machine.invoke(machine.getInitialState()); + expect(s.status).toBe('error'); + }); + + test('catches invoke errors', async () => { + const machine = createAgentMachine({ + id: 'err', + context: () => ({}), + initial: 'fail', + states: { + fail: { + invoke: async () => { + throw new Error('boom'); + }, + onDone: () => ({ target: 'ok' }), + }, + ok: { type: 'final' }, + }, + }); + const s = await machine.invoke(machine.getInitialState()); + expect(s.status).toBe('error'); + expect((s.error as Error).message).toBe('boom'); + }); - expect(agent.getMessages()).toContainEqual( - expect.objectContaining({ - content: [expect.objectContaining({ text: 'response 1' })], - sessionId: expect.any(String), - timestamp: expect.any(Number), - }) - ); }); -test('agent.addFeedback() adds to feedback', () => { - const agent = createAgent({ - name: 'test', - events: {}, - model: {} as any, +describe('transition', () => { + test('transitions on matching event', () => { + const machine = createSimpleMachine(); + const s = machine.transition(machine.getInitialState(), { type: 'start' }); + expect(s.value).toBe('running'); + expect(s.status).toBe('active'); + }); + + test('self-transition (no target)', async () => { + const machine = createHitlMachine(); + let s = await machine.invoke(machine.getInitialState({ task: 'x' })); + s = machine.transition(s, { type: 'user.message', message: 'hello' }); + expect(s.value).toBe('gathering'); + expect(s.context.messages[0]!.content).toBe('hello'); + }); + + test('accumulates context', async () => { + const machine = createHitlMachine(); + let s = await machine.invoke(machine.getInitialState({ task: 'x' })); + s = machine.transition(s, { type: 'user.message', message: 'one' }); + s = machine.transition(s, { type: 'user.message', message: 'two' }); + expect(s.context.messages.length).toBe(2); + }); + + test('throws on unknown event', () => { + const machine = createSimpleMachine(); + expect(() => + machine.transition(machine.getInitialState(), { type: 'nope' }) + ).toThrow("No handler for event 'nope'"); }); - const feedback = agent.addFeedback({ - attributes: { - score: -1, - }, - goal: 'Win the game', - observationId: 'obs-1', - }); - - expect(feedback.sessionId).toEqual(agent.sessionId); - - expect(agent.getFeedback()).toContainEqual( - expect.objectContaining({ - attributes: { - score: -1, - }, - goal: 'Win the game', - observationId: 'obs-1', - sessionId: expect.any(String), - timestamp: expect.any(Number), - }) - ); - expect(agent.getFeedback()).toContainEqual( - expect.objectContaining({ - attributes: { - score: -1, - }, - goal: 'Win the game', - observationId: 'obs-1', - sessionId: expect.any(String), - timestamp: expect.any(Number), - }) - ); }); -test('agent.addObservation() adds to observations', () => { - const agent = createAgent({ - name: 'test', - events: {}, - model: {} as any, +describe('execute', () => { + test('runs until done', async () => { + const machine = createSimpleMachine(); + let s = machine.transition(machine.getInitialState(), { type: 'start' }); + const r = await machine.execute(s); + expect(r.status).toBe('done'); + if (r.status === 'done') { + expect(r.output).toEqual({ result: 1 }); + expect(r.context.count).toBe(1); + } }); - const observation = agent.addObservation({ - prevState: { value: 'playing', context: {} }, - event: { type: 'play', position: 3 }, - state: { value: 'lost', context: {} }, + test('stops at pending', async () => { + const machine = createHitlMachine(); + const r = await machine.execute(machine.getInitialState({ task: 'x' })); + expect(r.status).toBe('pending'); + if (r.status === 'pending') { + expect(r.value).toBe('gathering'); + expect(r.events['user.message']).toBeDefined(); + } }); - expect(observation.sessionId).toEqual(agent.sessionId); + test('stops on error', async () => { + const machine = createAgentMachine({ + id: 'err', + context: () => ({}), + initial: 'fail', + states: { + fail: { + invoke: async () => { + throw new Error('nope'); + }, + onDone: () => ({ target: 'ok' }), + }, + ok: { type: 'final' }, + }, + }); + const r = await machine.execute(machine.getInitialState()); + expect(r.status).toBe('error'); + }); + + test('runs through multiple transitions', async () => { + const machine = createDecideMachine( + mockAdapter([{ choice: 'technical' }]) + ); + const r = await machine.execute(machine.getInitialState()); + expect(r.status).toBe('done'); + if (r.status === 'done') { + expect(r.output).toEqual({ + category: 'technical', + resolution: 'Handled technical issue', + }); + } + }); + +}); - expect(agent.getObservations()).toContainEqual( - expect.objectContaining({ - prevState: { value: 'playing', context: {} }, - event: { type: 'play', position: 3 }, - state: { value: 'lost', context: {} }, - sessionId: expect.any(String), - timestamp: expect.any(Number), - }) - ); +describe('stream', () => { + test('yields snapshots', async () => { + const machine = createDecideMachine( + mockAdapter([{ choice: 'technical' }]) + ); + const snaps = []; + for await (const snap of machine.stream(machine.getInitialState())) { + snaps.push(snap); + } + expect(snaps.length).toBeGreaterThanOrEqual(3); + expect(snaps[0]!.value).toBe('classifying'); + expect(snaps[snaps.length - 1]!.status).toBe('done'); + }); }); -test('agent.addObservation() adds to observations with machine hash', () => { - const agent = createAgent({ - name: 'test', - events: {}, - model: {} as any, +describe('resolveState', () => { + test('restores from JSON', async () => { + const machine = createHitlMachine(); + const r = await machine.execute(machine.getInitialState({ task: 'x' })); + const restored = machine.resolveState(JSON.parse(JSON.stringify(r.state))); + const next = machine.transition(restored, { + type: 'user.message', + message: 'restored', + }); + expect(next.context.messages[0]!.content).toBe('restored'); }); - const machine = createMachine({ - initial: 'playing', - states: { - playing: { - on: { - play: 'lost', +}); + +describe('decide', () => { + test('calls adapter with resolved prompt', async () => { + const spy = vi.fn().mockResolvedValue({ choice: 'a', data: {} }); + const machine = createAgentMachine({ + id: 'dtest', + context: () => ({ topic: 'cats', choice: null as string | null }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: decideResultSchema({ + a: { description: 'A' }, + b: { description: 'B' }, + }) }, + invoke: async ({ context }) => + decide({ + adapter: { decide: spy }, + model: 'my-model', + prompt: `About ${context.topic}`, + options: { a: { description: 'A' }, b: { description: 'B' } }, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { choice: output.choice }, + }), }, + done: { type: 'final' }, }, - lost: {}, - }, + }); + await machine.invoke(machine.getInitialState()); + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ model: 'my-model', prompt: 'About cats' }) + ); + }); + + test('per-state adapter override', async () => { + const machine = createAgentMachine({ + id: 'override', + context: () => ({ choice: null as string | null }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: decideResultSchema({ + state: { description: 'State' }, + machine: { description: 'Machine' }, + }) }, + invoke: async () => + decide({ + adapter: mockAdapter([{ choice: 'state' }]), + model: 'test', + prompt: 'pick', + options: { + state: { description: 'State' }, + machine: { description: 'Machine' }, + }, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { choice: output.choice }, + }), + }, + done: { type: 'final' }, + }, + }); + const r = await machine.execute(machine.getInitialState()); + expect(r.status === 'done' && r.context.choice).toBe('state'); + }); + + test('option schemas typed data', async () => { + const machine = createAgentMachine({ + id: 'data', + context: () => ({ items: null as string[] | null }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: decideResultSchema({ + withData: { + description: 'Has data', + schema: z.object({ items: z.array(z.string()) }), + }, + withoutData: { description: 'No data' }, + }) }, + invoke: async () => + decide({ + adapter: { + decide: async () => ({ + choice: 'withData', + data: { items: ['a', 'b'] }, + }), + }, + model: 'test', + prompt: 'pick', + options: { + withData: { + description: 'Has data', + schema: z.object({ items: z.array(z.string()) }), + }, + withoutData: { description: 'No data' }, + }, + }), + onDone: ({ output }) => { + return { + target: 'done', + context: { + items: + output.choice === 'withData' + ? (output.data.items ?? null) + : null, + }, + }; + }, + }, + done: { type: 'final' }, + }, + }); + const r = await machine.execute(machine.getInitialState()); + expect(r.status === 'done' && r.context.items).toEqual(['a', 'b']); }); +}); - const observation = agent.addObservation({ - prevState: { value: 'playing', context: {} }, - event: { type: 'play', position: 3 }, - state: { value: 'lost', context: {} }, - machine, +describe('decide helper', () => { + test('explicit decide invoke with typed context', async () => { + const adapter = mockAdapter([{ choice: 'technical' }]); + const machine = createAgentMachine({ + id: 'decide-helper-test', + context: () => ({ issue: 'App crashes', result: null as string | null }), + initial: 'routing', + states: { + routing: { + schemas: { output: choiceResultSchema }, + invoke: async ({ context }) => + decide({ + adapter, + model: 'test-model', + prompt: `Route: ${context.issue}`, + options: { + billing: { description: 'Billing' }, + technical: { description: 'Technical' }, + }, + }), + onDone: ({ output, context }) => ({ + target: 'done', + context: { result: `${output.choice}: ${context.issue}` }, + }), + }, + done: { type: 'final', output: ({ context }) => ({ result: context.result }) }, + }, + }); + + const r = await machine.execute(machine.getInitialState()); + expect(r.status).toBe('done'); + if (r.status === 'done') { + expect(r.output).toEqual({ result: 'technical: App crashes' }); + } }); - expect(observation.sessionId).toEqual(agent.sessionId); + test('invoke state with event transition', () => { + let called = false; + const adapter: DecideAdapter = { + decide: async () => { + called = true; + return { choice: 'a', data: {} }; + }, + }; + const machine = createAgentMachine({ + id: 'invoke-event-transition', + context: () => ({}), + initial: 'choosing', + states: { + choosing: { + schemas: { output: choiceResultSchema }, + invoke: async () => + decide({ + adapter, + model: 'test', + prompt: 'pick', + options: { a: { description: 'A' } }, + }), + onDone: () => ({ target: 'done' }), + on: { + cancel: () => ({ target: 'cancelled' }), + }, + }, + done: { type: 'final' }, + cancelled: { type: 'final' }, + }, + }); - expect(agent.getObservations()).toContainEqual( - expect.objectContaining({ - prevState: { value: 'playing', context: {} }, - event: { type: 'play', position: 3 }, - state: { value: 'lost', context: {} }, - machineHash: expect.any(String), - sessionId: expect.any(String), - timestamp: expect.any(Number), - }) - ); + const state = machine.getInitialState(); + const next = machine.transition(state, { type: 'cancel' }); + expect(next.value).toBe('cancelled'); + expect(called).toBe(false); + }); }); -test('agent.interact() observes machine actors (no 2nd arg)', () => { - const machine = createMachine({ - initial: 'a', - states: { - a: { - on: { NEXT: 'b' }, +describe('messages and always', () => { + test('states expose resolved generation fields', () => { + const search = async () => 'result'; + const machine = createAgentMachine({ + id: 'generation-fields', + schemas: { + input: z.object({ task: z.string() }), }, - b: {}, - }, + context: (input) => ({ task: input.task, phase: 'read' }), + messages: (input) => [{ role: 'user', content: input.task }], + initial: 'planning', + states: { + planning: { + model: 'test-model', + system: 'Plan carefully.', + prompt: ({ context }) => `Plan: ${context.task}`, + tools: { search }, + toolChoice: 'auto', + on: { + ready: { + target: 'implementing', + context: { phase: 'write' }, + messages: [ + { + role: 'system', + content: 'Writing is allowed now.', + }, + ], + }, + }, + }, + implementing: { + prompt: ({ context }) => `Implement: ${context.task}`, + tools: { + writeFile: async () => 'ok', + }, + on: { + done: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); + + const planning = machine.getInitialState({ task: 'Fix bug' }); + expect(planning.prompt).toBe('Plan: Fix bug'); + expect(planning.model).toBe('test-model'); + expect(planning.system).toBe('Plan carefully.'); + expect(Object.keys(planning.tools ?? {})).toEqual(['search', 'event.ready']); + expect(planning.toolChoice).toBe('auto'); + + const implementing = machine.transition(planning, { type: 'ready' }); + expect(implementing.prompt).toBe('Implement: Fix bug'); + expect(implementing.model).toBeUndefined(); + expect(Object.keys(implementing.tools ?? {})).toEqual([ + 'writeFile', + 'event.done', + ]); + expect(implementing.messages.at(-1)).toEqual({ + role: 'system', + content: 'Writing is allowed now.', + }); + }); + + test('generation fields resolve from the unresolved snapshot', () => { + const read = async () => 'read'; + const write = async () => 'write'; + const seenSnapshots: Array<{ + value: string; + hasPrompt: boolean; + hasTools: boolean; + }> = []; + const machine = createAgentMachine({ + id: 'snapshot-resolvers', + schemas: { + input: z.object({ task: z.string(), mode: z.enum(['read', 'write']) }), + }, + context: (input) => ({ task: input.task, mode: input.mode }), + messages: (input) => [{ role: 'user', content: `Task: ${input.task}` }], + initial: 'working', + states: { + working: { + model: ({ snapshot }) => + snapshot.context.mode === 'write' ? 'write-model' : 'read-model', + system: ({ snapshot }) => `State: ${snapshot.value}`, + prompt: ({ snapshot }) => { + seenSnapshots.push({ + value: snapshot.value, + hasPrompt: 'prompt' in snapshot, + hasTools: 'tools' in snapshot, + }); + + return [ + `Mode: ${snapshot.context.mode}`, + `Messages: ${snapshot.messages.length}`, + `Task: ${snapshot.context.task}`, + ].join('\n'); + }, + tools: ({ snapshot }) => + snapshot.context.mode === 'write' ? { read, write } : { read }, + toolChoice: ({ snapshot }) => + snapshot.context.mode === 'write' ? 'required' : 'auto', + on: { + done: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); + + const state = machine.getInitialState({ task: 'Fix bug', mode: 'write' }); + + expect(state.model).toBe('write-model'); + expect(state.system).toBe('State: working'); + expect(state.prompt).toBe('Mode: write\nMessages: 1\nTask: Fix bug'); + expect(Object.keys(state.tools ?? {})).toEqual(['read', 'write', 'event.done']); + expect(state.toolChoice).toBe('required'); + expect(seenSnapshots).toEqual([ + { + value: 'working', + hasPrompt: false, + hasTools: false, + }, + ]); }); - const agent = createAgent({ - name: 'test', - events: {}, - model: {} as any, + test('event tools are namespaced and use event schemas', async () => { + const userTool = async () => 'user tool'; + const machine = createAgentMachine({ + id: 'event-tools', + schemas: { + events: { + PLAN_READY: z.object({ + type: z.literal('PLAN_READY'), + rationale: z.string(), + }), + }, + }, + context: () => ({}), + initial: 'planning', + states: { + planning: { + tools: { PLAN_READY: userTool }, + on: { + PLAN_READY: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); + + const state = machine.getInitialState(); + expect(state.tools?.PLAN_READY).toBe(userTool); + expect(state.tools?.['event.PLAN_READY']).toMatchObject({ + description: "Transition with event 'PLAN_READY'.", + schemas: { input: expect.any(Object) }, + }); + + const eventTool = state.tools?.['event.PLAN_READY'] as { + execute(input: Record): Promise>; + }; + await expect( + eventTool.execute({ rationale: 'plan is ready' }) + ).resolves.toEqual({ + type: 'PLAN_READY', + rationale: 'plan is ready', + }); }); - const actor = createActor(machine); + test('prompt states with no user tools still expose event tools', () => { + const machine = createAgentMachine({ + id: 'event-only-tools', + context: () => ({}), + initial: 'waiting', + states: { + waiting: { + prompt: 'Wait for completion.', + on: { + done: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); - agent.interact(actor); + expect(Object.keys(machine.getInitialState().tools ?? {})).toEqual([ + 'event.done', + ]); + }); - actor.start(); + test('on events become prefixed event tools in prompt states by default', () => { + const machine = createAgentMachine({ + id: 'prefixed-event-tools', + context: () => ({}), + initial: 'planning', + states: { + planning: { + prompt: 'Plan and choose a transition.', + on: { + PLAN_READY: { target: 'done' }, + FAIL: { target: 'failed' }, + }, + }, + done: { type: 'final' }, + failed: { type: 'final' }, + }, + }); - expect(agent.getObservations()).toContainEqual( - expect.objectContaining({ - prevState: undefined, - state: expect.objectContaining({ value: 'a' }), - }) - ); - expect(agent.getObservations()).toContainEqual( - expect.objectContaining({ - prevState: undefined, - state: expect.objectContaining({ value: 'a' }), - }) - ); + expect(Object.keys(machine.getInitialState().tools ?? {})).toEqual([ + 'event.PLAN_READY', + 'event.FAIL', + ]); + }); - actor.send({ type: 'NEXT' }); + test('non-generative states do not expose on events as tools', () => { + const machine = createAgentMachine({ + id: 'non-generative-events', + context: () => ({}), + initial: 'waiting', + states: { + waiting: { + on: { + APPROVED: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); - expect(agent.getObservations()).toContainEqual( - expect.objectContaining({ - prevState: expect.objectContaining({ value: 'a' }), - event: { type: 'NEXT' }, - state: expect.objectContaining({ value: 'b' }), - }) - ); + const waiting = machine.getInitialState(); + expect(waiting.tools).toBeUndefined(); + + const done = machine.transition(waiting, { type: 'APPROVED' }); + expect(done.value).toBe('done'); + }); + + test('external events are valid transitions but excluded from event tools', () => { + const machine = createAgentMachine({ + id: 'external-events', + externalEvents: ['APPROVED', 'REJECTED'], + schemas: { + events: { + PLAN_READY: z.object({}), + APPROVED: z.object({}), + REJECTED: z.object({}), + }, + }, + context: () => ({}), + initial: 'planning', + states: { + planning: { + prompt: 'Prepare a plan.', + on: { + PLAN_READY: { target: 'awaitingApproval' }, + }, + }, + awaitingApproval: { + prompt: 'Wait for approval.', + on: { + APPROVED: { target: 'done' }, + REJECTED: { target: 'planning' }, + }, + }, + done: { type: 'final' }, + }, + }); + + const planning = machine.getInitialState(); + expect(Object.keys(planning.tools ?? {})).toEqual(['event.PLAN_READY']); + + const awaitingApproval = machine.transition(planning, { + type: 'PLAN_READY', + }); + expect(awaitingApproval.value).toBe('awaitingApproval'); + expect(awaitingApproval.tools).toBeUndefined(); + + const done = machine.transition(awaitingApproval, { type: 'APPROVED' }); + expect(done.value).toBe('done'); + }); + + test('invoke cannot be combined with generation fields', () => { + expect(() => + createAgentMachine({ + id: 'invoke-generation-conflict', + context: () => ({}), + initial: 'working', + states: { + working: { + prompt: 'Generate something.', + invoke: async () => ({}), + }, + }, + }) + ).toThrow( + "State 'working' cannot combine invoke with prompt, system, tools, or toolChoice" + ); + }); + + test('snapshots omit executable generation fields', async () => { + const machine = createAgentMachine({ + id: 'snapshot-generation-fields', + context: () => ({}), + initial: 'waiting', + states: { + waiting: { + prompt: 'Use the tool.', + tools: { search: async () => 'result' }, + on: { done: { target: 'done' } }, + }, + done: { type: 'final' }, + }, + }); + + const state = machine.getInitialState(); + expect(state.prompt).toBe('Use the tool.'); + expect(state.tools).toBeDefined(); + + const snapshots = []; + for await (const snapshot of machine.stream(state)) { + snapshots.push(snapshot); + break; + } + + expect(snapshots[0]).not.toHaveProperty('prompt'); + expect(snapshots[0]).not.toHaveProperty('tools'); + }); + + test('messages are passed through invoke, onDone, always, and output', async () => { + const machine = createAgentMachine({ + id: 'messages-always', + schemas: { + input: z.object({ prompt: z.string() }), + output: z.object({ + messages: z.array(z.object({ role: z.string(), content: z.string() })), + attempts: z.number(), + }), + }, + context: () => ({ + attempts: 0, + accepted: false, + }), + messages: (input) => [{ role: 'user', content: input.prompt }], + initial: 'generating', + states: { + generating: { + schemas: { output: z.object({ text: z.string() }) }, + invoke: async ({ messages }) => ({ + text: `reply to ${messages.at(-1)?.content}`, + }), + onDone: ({ output, context, messages }) => ({ + target: 'checking', + context: { attempts: context.attempts + 1 }, + messages: messages.concat({ + role: 'assistant', + content: output.text, + }), + }), + }, + checking: { + always: ({ context, messages }) => + context.attempts >= 2 + ? { + target: 'done', + context: { accepted: true }, + messages: messages.concat({ + role: 'system', + content: 'accepted', + }), + } + : { + target: 'generating', + messages: messages.concat({ + role: 'user', + content: 'repair', + }), + }, + }, + done: { + type: 'final', + output: ({ context, messages }) => ({ + messages, + attempts: context.attempts, + }), + }, + }, + }); + + const result = await machine.execute(machine.getInitialState({ prompt: 'draft' })); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.messages.map((message) => message.content)).toEqual([ + 'draft', + 'reply to draft', + 'repair', + 'reply to repair', + 'accepted', + ]); + expect(result.output.attempts).toBe(2); + } + }); }); -test('You can listen for feedback events', () => { - const fn = vi.fn(); - const agent = createAgent({ - name: 'test', - events: {}, - model: {} as any, +describe('classify', () => { + test('result has typed category', async () => { + const machine = createClassifyMachine( + mockAdapter([{ choice: 'billing' }]) + ); + const r = await machine.execute(machine.getInitialState()); + expect(r.status === 'done' && r.output).toEqual({ category: 'billing' }); }); +}); - agent.on('feedback', fn); +describe('P2: event validation', () => { + test('rejects invalid payload', async () => { + const machine = createHitlMachine(); + const s = await machine.invoke(machine.getInitialState({ task: 'x' })); + expect(() => + // @ts-expect-error — deliberately invalid for runtime test + machine.transition(s, { type: 'user.message', message: 123 }) + ).toThrow(); + }); - agent.addFeedback({ - attributes: { - score: -1, - }, - goal: 'Win the game', - observationId: 'obs-1', + test('accepts valid payload', async () => { + const machine = createHitlMachine(); + const s = await machine.invoke(machine.getInitialState({ task: 'x' })); + const next = machine.transition(s, { + type: 'user.message', + message: 'ok', + }); + expect(next.context.messages.length).toBe(1); }); - expect(fn).toHaveBeenCalled(); + test('skips when no schema', () => { + const machine = createSimpleMachine(); + const s = machine.transition(machine.getInitialState(), { type: 'start' }); + expect(s.value).toBe('running'); + }); }); -test('You can listen for plan events', async () => { - const fn = vi.fn(); - const model = new MockLanguageModelV1({ - doGenerate: async (params: LanguageModelV1CallOptions) => { - const keys = - params.mode.type === 'regular' - ? params.mode.tools?.map((t) => t.name) - : []; +describe('full HITL workflow', () => { + test('gather → process → review → done', async () => { + const machine = createHitlMachine(); + let s = machine.getInitialState({ task: 'build' }); + let r = await machine.execute(s); + expect(r.status).toBe('pending'); - return { - ...dummyResponseValues, - finishReason: 'tool-calls', - toolCalls: [ - { - toolCallType: 'function', - toolCallId: 'call-1', - toolName: keys![0], - args: `{ "type": "${keys?.[0]}" }`, - }, - ], - } as any; - }, + s = machine.transition(r.state, { + type: 'user.message', + message: 'req A', + }); + s = machine.transition(s, { type: 'user.message', message: 'req B' }); + s = machine.transition(s, { type: 'user.approve' }); + r = await machine.execute(s); + expect(r.status === 'pending' && r.context.result).toBe( + 'Processed: req A, req B' + ); + + s = machine.transition(r.state, { type: 'user.approve' }); + r = await machine.execute(s); + expect(r.status === 'done' && r.output).toEqual({ + result: 'Processed: req A, req B', + }); }); - const agent = createAgent({ - name: 'test', - model, - events: { - WIN: z.object({}), - }, + test('cancel', async () => { + const machine = createHitlMachine(); + let r = await machine.execute(machine.getInitialState({ task: 'x' })); + const s = machine.transition(r.state, { type: 'user.cancel' }); + r = await machine.execute(s); + expect(r.status === 'done' && r.output).toEqual({ cancelled: true }); }); +}); - agent.on('plan', fn); +describe('type inference', () => { + // ─── state.value ─── - await agent.decide({ - goal: 'Win the game', - state: { - value: 'playing', - context: {}, - }, - machine: createMachine({ - initial: 'playing', + test('state.value is typed union of state names', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ x: 1 }), + initial: 'a', states: { - playing: { + a: { on: { go: () => ({ target: 'b' }) } }, + b: { type: 'final' }, + }, + }); + const s = machine.getInitialState(); + + s.value satisfies 'a' | 'b'; + // @ts-expect-error — 'c' is not a valid state name + s.value satisfies 'c'; + }); + + // ─── state.context ─── + + test('context typed from context() return', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ name: 'test', count: 0, flag: true }), + initial: 'idle', + states: { idle: { type: 'final' } }, + }); + const s = machine.getInitialState(); + + s.context.name satisfies string; + s.context.count satisfies number; + s.context.flag satisfies boolean; + // @ts-expect-error — name is string not number + s.context.name satisfies number; + // @ts-expect-error — 'nope' does not exist + s.context.nope; + }); + + test('transition context is Partial — rejects unknown keys', () => { + createAgentMachine({ + id: 't', + schemas: { events: { go: z.object({}) } }, + context: () => ({ count: 0, name: 'hello' }), + initial: 'idle', + states: { + idle: { on: { - WIN: { - target: 'won', + go: ({ context }) => ({ + target: 'idle', + // valid: known key + context: { count: context.count + 1 }, + }), + }, + }, + }, + }); + + createAgentMachine({ + id: 't2', + schemas: { events: { go: z.object({}) } }, + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error — 'foo' not a valid context key + go: () => ({ + target: 'idle', + context: { foo: 'bar' }, + }), + }, + }, + }, + }); + }); + + test('context typed in on handlers', () => { + const machine = createAgentMachine({ + id: 't', + schemas: { events: { add: z.object({}) } }, + context: () => ({ items: ['a', 'b'] }), + initial: 'idle', + states: { + idle: { + on: { + add: ({ context }) => { + context.items satisfies string[]; + // @ts-expect-error — 'nope' does not exist + context.nope; + return { context: { items: [...context.items, 'c'] } }; }, }, }, - won: {}, }, - }), + }); + const next = machine.transition(machine.getInitialState(), { type: 'add' }); + expect(next.context.items).toEqual(['a', 'b', 'c']); + }); + + test('context typed in invoke', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ n: 42 }), + initial: 'work', + states: { + work: { + schemas: { output: z.object({ doubled: z.number() }) }, + invoke: async ({ context }) => { + context.n satisfies number; + // @ts-expect-error — 'nope' does not exist + context.nope; + return { doubled: context.n * 2 }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { n: output.doubled }, + }), + }, + done: { type: 'final' }, + }, + }); + return machine.execute(machine.getInitialState()).then((r) => { + expect(r.status === 'done' && r.context.n).toBe(84); + }); }); - expect(fn).toHaveBeenCalledWith( - expect.objectContaining({ - plan: expect.objectContaining({ - nextEvent: { - type: 'WIN', + test('context typed in output', () => { + const machine = createAgentMachine({ + id: 't', + schemas: { + output: z.object({ + score: z.number(), + }), + }, + context: () => ({ score: 100 }), + initial: 'done', + states: { + done: { + type: 'final', + output: ({ context }) => { + context.score satisfies number; + // @ts-expect-error — 'nope' does not exist + context.nope; + return { score: context.score }; + }, }, - }), - }) - ); -}); + }, + }); + expect(machine.getInitialState).toBeDefined(); + }); -test('agent.types provides context and event types', () => { - const agent = createAgent({ - model: {} as any, - events: { - setScore: z.object({ - score: z.number(), - }), - }, - context: { - score: z.number(), - }, + test('context typed in initial function', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ mode: 'fast' as 'fast' | 'slow' }), + initial: ({ context }) => { + context.mode satisfies 'fast' | 'slow'; + // @ts-expect-error — 'nope' does not exist + context.nope; + return { target: (context.mode === 'fast' ? 'a' : 'b') as 'a' | 'b' }; + }, + states: { + a: { type: 'final' }, + b: { type: 'final' }, + }, + }); + expect(machine.getInitialState().value).toBe('a'); }); - agent.types satisfies { context: any; events: any }; + // ─── schemas.context (overload 1) ─── - agent.types.context satisfies { score: number }; + test('schemas.context drives TContext + input typed from schemas.input', () => { + const machine = createAgentMachine({ + id: 't', + schemas: { + context: z.object({ count: z.number(), label: z.string() }), + input: z.object({ initial: z.number() }), + }, + context: (input) => { + input.initial satisfies number; + // @ts-expect-error — 'nope' does not exist on input + input.nope; + return { count: input.initial, label: 'hello' }; + }, + initial: 'idle', + states: { + idle: { + invoke: async ({ context }) => { + context.count satisfies number; + context.label satisfies string; + // @ts-expect-error — 'nope' does not exist + context.nope; + return {}; + }, + }, + }, + }); + const s = machine.getInitialState({ initial: 5 }); + + s.context.count satisfies number; + s.context.label satisfies string; + // @ts-expect-error — 'nope' does not exist + s.context.nope; + expect(s.context.count).toBe(5); + }); - // @ts-expect-error - agent.types.context satisfies { score: string }; + test('schemas.input alone drives context input typing', () => { + const machine = createAgentMachine({ + id: 't-input-only', + schemas: { + input: z.object({ message: z.string() }), + }, + context: (input) => { + input.message satisfies string; + // @ts-expect-error — 'nope' does not exist on input + input.nope; + return { message: input.message, count: 0 }; + }, + initial: 'idle', + states: { + idle: { + type: 'final', + }, + }, + }); + + machine.getInitialState({ message: 'hello' }); + if (false) { + // @ts-expect-error — message must be string + machine.getInitialState({ message: 123 }); + } + }); + + // ─── schemas.events ─── + + test('transition events typed from schemas.events', () => { + const machine = createAgentMachine({ + id: 't', + schemas: { + events: { + greet: z.object({ name: z.string() }), + ping: z.object({}), + }, + }, + context: () => ({ msg: '' }), + initial: 'idle', + states: { + idle: { + on: { + greet: ({ event }) => ({ + context: { msg: `hi ${event.name}` }, + }), + ping: () => ({}), + }, + }, + }, + }); + const s = machine.getInitialState(); + + // Valid events compile + machine.transition(s, { type: 'greet', name: 'world' }); + machine.transition(s, { type: 'ping' }); + + // @ts-expect-error — 'bogus' is not a valid event type + expect(() => machine.transition(s, { type: 'bogus' })).toThrow(); + + // @ts-expect-error — missing required 'name' field + expect(() => machine.transition(s, { type: 'greet' })).toThrow(); + + expect(() => + machine.transition(s, { + type: 'greet', + // @ts-expect-error — name must be string + name: 123, + }) + ).toThrow(); + + const next = machine.transition(s, { type: 'greet', name: 'world' }); + expect(next.context.msg).toBe('hi world'); + }); + + test('no schemas.events → untyped events (any type string)', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({}), + initial: 'idle', + states: { + idle: { on: { anything: () => ({}) } }, + }, + }); + // Any event type string accepted when no schemas.events + machine.transition(machine.getInitialState(), { type: 'anything' }); + // Unknown events still throw at runtime (no handler) + expect(() => + machine.transition(machine.getInitialState(), { type: 'nope' }) + ).toThrow(); + }); + + // ─── schemas.input per state ─── + + test('input typed per state from schemas.input', async () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ result: '' }), + initial: 'a', + states: { + a: { + schemas: { input: z.object({ count: z.number() }), output: z.object({ doubled: z.number() }) }, + invoke: async ({ input }) => { + input.count satisfies number; + // @ts-expect-error — count is number not string + input.count satisfies string; + // @ts-expect-error — 'name' not on a's input + input.name; + return { doubled: input.count * 2 }; + }, + onDone: ({ output }) => ({ + target: 'b', + input: { name: 'hello' }, + context: { result: String(output.doubled) }, + }), + }, + b: { + schemas: { input: z.object({ name: z.string() }), output: z.object({ greeting: z.string() }) }, + invoke: async ({ input }) => { + input.name satisfies string; + // @ts-expect-error — name is string not number + input.name satisfies number; + // @ts-expect-error — 'count' not on b's input + input.count; + return { greeting: `hi ${input.name}` }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { result: output.greeting }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ result: context.result }), + }, + }, + }); + + let state = machine.resolveState({ + ...machine.getInitialState(), + input: { a: { count: 21 } }, + }); + const r = await machine.execute(state); + expect(r.status === 'done' && r.output).toEqual({ result: 'hi hello' }); + }); + + test('no schemas.input → input is Record', () => { + createAgentMachine({ + id: 't', + context: () => ({}), + initial: 'idle', + states: { + idle: { + invoke: async ({ input }) => { + input satisfies Record; + return {}; + }, + }, + }, + }); + }); + + test('state resolver snapshot is typed from context and input', () => { + createAgentMachine({ + id: 't', + schemas: { + input: z.object({ task: z.string() }), + }, + context: (input) => ({ task: input.task, count: 1 }), + initial: 'working', + states: { + working: { + schemas: { input: z.object({ attempt: z.number() }) }, + prompt: ({ snapshot, context, input }) => { + snapshot.value satisfies string; + snapshot.context.task satisfies string; + context.count satisfies number; + input.attempt satisfies number; + // @ts-expect-error — resolved prompt is not present while resolving + snapshot.prompt; + // @ts-expect-error — attempt is number not string + input.attempt satisfies string; + return `${snapshot.value}: ${context.task}`; + }, + on: { + done: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); + }); + + // ─── decide helper context typing ─── + + test('decide helper gets typed context in invoke and onDone', () => { + const adapter = mockAdapter([{ choice: 'a' }]); + const machine = createAgentMachine({ + id: 't', + context: () => ({ topic: 'cats', result: '' }), + initial: 'choosing', + states: { + choosing: { + schemas: { output: choiceResultSchema }, + invoke: async ({ context }) => { + context.topic satisfies string; + // @ts-expect-error — 'nope' does not exist + context.nope; + return decide({ + adapter, + model: 'test', + prompt: `About ${context.topic}`, + options: { a: { description: 'A' } }, + }); + }, + onDone: ({ output, context }) => { + output.choice satisfies string; + // @ts-expect-error + output.nope; + context.topic satisfies string; + return { target: 'done', context: { result: output.choice } }; + }, + }, + done: { type: 'final' }, + }, + }); + expect(machine.id).toBe('t'); + }); + + // ─── getInitialState input typing ─── + + test('getInitialState requires input when schemas.input provided', () => { + const machine = createAgentMachine({ + id: 't', + schemas: { + context: z.object({ task: z.string() }), + input: z.object({ task: z.string() }), + }, + context: (input) => ({ task: input.task }), + initial: 'idle', + states: { idle: { type: 'final' } }, + }); + + // Valid + machine.getInitialState({ task: 'hello' }); + + expect(() => + machine.getInitialState({ + // @ts-expect-error — task must be string + task: 123, + }) + ).toThrow(); + + // @ts-expect-error — missing required input (runtime: validates) + expect(() => machine.getInitialState()).toThrow(); + }); + + test('getInitialState optional when no input schema', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ x: 1 }), + initial: 'idle', + states: { idle: { type: 'final' } }, + }); + + // Both valid + machine.getInitialState(); + machine.getInitialState(undefined); + }); + + // ─── schemas.output ─── + + test('schemas.output types invoke return and onDone output', () => { + createAgentMachine({ + id: 't', + context: () => ({ total: 0 }), + initial: 'work', + states: { + work: { + schemas: { output: z.object({ value: z.number() }) }, + invoke: async () => { + // return type must match schemas.output + return { value: 42 }; + }, + onDone: ({ output }) => { + // output is typed from schemas.output + output.value satisfies number; + // @ts-expect-error — 'nope' does not exist on result + output.nope; + return { target: 'done', context: { total: output.value } }; + }, + }, + done: { type: 'final' }, + }, + }); + }); + + test('no schemas.output → onDone output is inferred from invoke', () => { + createAgentMachine({ + id: 't', + context: () => ({}), + initial: 'work', + states: { + work: { + invoke: async () => ({ anything: true }), + onDone: ({ output }) => { + output.anything satisfies boolean; + // @ts-expect-error — 'choice' does not exist on invoke result + output.choice; + return { target: 'done' }; + }, + }, + done: { type: 'final' }, + }, + }); + }); + + test('final output is inferred through execute and snapshots', async () => { + const machine = createAgentMachine({ + id: 'typed-output', + schemas: { + output: z.object({ + count: z.number(), + label: z.string(), + }), + }, + context: () => ({ count: 2 }), + initial: 'done', + states: { + done: { + type: 'final', + output: ({ context }) => ({ + count: context.count, + label: `count:${context.count}`, + }), + }, + }, + }); + + const runResult = await machine.execute(machine.getInitialState()); + if (runResult.status === 'done') { + runResult.output.count satisfies number; + runResult.output.label satisfies string; + // @ts-expect-error output property should be typed + runResult.output.missing; + } + + const snapshot = machine.resolveState(machine.getInitialState()); + snapshot.output satisfies + | { + count: number; + label: string; + } + | undefined; + }); + + // ─── events typed in on handlers ─── + + test('on handler event typed from schemas.events', () => { + createAgentMachine({ + id: 't', + schemas: { + events: { + 'msg': z.object({ text: z.string() }), + }, + }, + context: () => ({ last: '' }), + initial: 'idle', + states: { + idle: { + on: { + msg: ({ event }) => { + // event.text is typed from schemas.events + event.text satisfies string; + event.type satisfies 'msg'; + return { context: { last: event.text } }; + }, + }, + }, + }, + }); + }); + + // ─── static transition shorthand ─── + + test('on handler accepts string shorthand', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({}), + initial: 'a', + states: { + a: { + on: { + go: { target: 'b' }, + }, + }, + b: { type: 'final' }, + }, + }); + const s = machine.transition(machine.getInitialState(), { type: 'go' }); + expect(s.value).toBe('b'); + }); + + test('on handler accepts static TransitionResult object', () => { + const machine = createAgentMachine({ + id: 't', + context: () => ({ x: 0 }), + initial: 'a', + states: { + a: { + on: { + go: { target: 'b', context: { x: 1 } }, + }, + }, + b: { type: 'final' }, + }, + }); + const s = machine.transition(machine.getInitialState(), { type: 'go' }); + expect(s.value).toBe('b'); + expect(s.context.x).toBe(1); + }); +}); + +describe('edge cases', () => { + test('invoke with no onDone is dead end', async () => { + const machine = createAgentMachine({ + id: 'dead', + context: () => ({}), + initial: 'stuck', + states: { stuck: { invoke: async () => ({}) } }, + }); + const s = await machine.invoke(machine.getInitialState()); + expect(s.value).toBe('stuck'); + }); + + test('done state returns as-is', async () => { + const machine = createSimpleMachine(); + const done = { + value: 'done' as const, + input: {}, + context: { count: 1 }, + messages: [], + status: 'done' as const, + output: { result: 1 }, + }; + expect(await machine.invoke(done)).toEqual(done); + }); +}); + +describe('createAdapter', () => { + test('creates custom adapter', () => { + const a = createAdapter({ + generateText: async () => 'ok', + }); + expect(a.generateText).toBeDefined(); + expect('decide' in a).toBe(false); + }); }); diff --git a/src/agent.ts b/src/agent.ts deleted file mode 100644 index 74f3a45..0000000 --- a/src/agent.ts +++ /dev/null @@ -1,687 +0,0 @@ -import { - Actor, - AnyActorRef, - AnyEventObject, - AnyStateMachine, - EventObject, - fromTransition, - Subscription, -} from 'xstate'; -import { ZodContextMapping, ZodEventMapping } from './schemas'; -import { - AgentLogic, - AgentMessage, - AgentPlanner, - EventsFromZodEventMapping, - GenerateTextOptions, - AgentLongTermMemory, - ObservedState, - AgentObservationInput, - AgentMemoryContext, - AgentObservation, - ContextFromZodContextMapping, - AgentFeedback, - AgentMessageInput, - AgentFeedbackInput, - AgentPlan, - AnyAgent, - Compute, - AgentDecisionInput, - AgentDecideOptions, -} from './types'; -import { simplePlanner } from './planners/simplePlanner'; -import { agentDecide } from './decision'; -import { getMachineHash, randomId } from './utils'; -import { - experimental_wrapLanguageModel, - LanguageModel, - LanguageModelV1, -} from 'ai'; -import { createAgentMiddleware } from './middleware'; - -export const agentLogic: AgentLogic = fromTransition( - (state, event, { emit }) => { - switch (event.type) { - case 'agent.feedback': { - state.feedback.push(event.feedback); - emit({ - type: 'feedback', - // @ts-ignore TODO: fix types in XState - feedback: event.feedback, - }); - break; - } - case 'agent.observe': { - state.observations.push(event.observation); - emit({ - type: 'observation', - // @ts-ignore TODO: fix types in XState - observation: event.observation, - }); - break; - } - case 'agent.message': { - state.messages.push(event.message); - emit({ - type: 'message', - // @ts-ignore TODO: fix types in XState - message: event.message, - }); - break; - } - case 'agent.plan': { - state.plans.push(event.plan); - emit({ - type: 'plan', - // @ts-ignore TODO: fix types in XState - plan: event.plan, - }); - break; - } - default: - break; - } - return state; - }, - () => - ({ - feedback: [], - messages: [], - observations: [], - plans: [], - } as AgentMemoryContext) -); - -export function createAgent< - const TContextSchema extends ZodContextMapping, - const TEventSchemas extends ZodEventMapping, - TEvents extends EventObject = EventsFromZodEventMapping, - TContext = ContextFromZodContextMapping ->({ - id, - name, - description, - model, - events, - context, - planner = simplePlanner as AgentPlanner>, - stringify = JSON.stringify, - getMemory, - logic = agentLogic as AgentLogic, - ...generateTextOptions -}: { - /** - * The unique identifier for the agent. - * - * This should be the same across all sessions of a specific agent, as it can be - * used to retrieve memory for this agent. - * - * @example - * ```ts - * const agent = createAgent({ - * id: 'recipe-assistant', - * // ... - * }); - * ``` - */ - id?: string; - /** - * The name of the agent - */ - name?: string; - /** - * A description of the role of the agent - */ - description?: string; - /** - * Events that the agent can cause (send) in an environment - * that the agent knows about. - */ - events: TEventSchemas; - context?: TContextSchema; - planner?: AgentPlanner>; - stringify?: typeof JSON.stringify; - /** - * A function that retrieves the agent's long term memory - */ - getMemory?: ( - agent: Agent - ) => AgentLongTermMemory; - /** - * Agent logic - */ - logic?: AgentLogic; -} & GenerateTextOptions): Agent { - return new Agent({ - id, - context, - events, - name, - description, - planner, - model, - logic, - }) as any; - // const agent = createActor(logic) as unknown as Agent; - // agent.events = events; - // agent.model = model; - // agent.name = name; - // agent.description = description; - // agent.defaultOptions = { ...generateTextOptions, model }; - // agent.memory = getMemory ? getMemory(agent) : undefined; - - // agent.onMessage = (callback) => { - // agent.on('message', (ev) => callback(ev.message)); - // }; - - // agent.decide = (opts) => { - // return agentDecide(agent, opts); - // }; - - // agent.addMessage = (messageInput) => { - // const message = { - // ...messageInput, - // id: messageInput.id ?? randomId(), - // timestamp: messageInput.timestamp ?? Date.now(), - // sessionId: agent.sessionId, - // } satisfies AgentMessage; - // agent.send({ - // type: 'agent.message', - // message, - // }); - - // return message; - // }; - // agent.getMessages = () => agent.getSnapshot().context.messages; - - // agent.addFeedback = (feedbackInput) => { - // const feedback = { - // ...feedbackInput, - // attributes: { ...feedbackInput.attributes }, - // reward: feedbackInput.reward ?? 0, - // timestamp: feedbackInput.timestamp ?? Date.now(), - // sessionId: agent.sessionId, - // } satisfies AgentFeedback; - // agent.send({ - // type: 'agent.feedback', - // feedback, - // }); - // return feedback; - // }; - // agent.getFeedback = () => agent.getSnapshot().context.feedback; - - // agent.addObservation = (observationInput) => { - // const { prevState, event, state } = observationInput; - // const observedState = { context: state.context, value: state.value }; - // const observedPrevState = prevState - // ? { - // context: prevState.context, - // value: prevState.value, - // } - // : undefined; - // const observation = { - // prevState: observedPrevState, - // event, - // state: observedState, - // id: observationInput.id ?? randomId(), - // sessionId: agent.sessionId, - // timestamp: observationInput.timestamp ?? Date.now(), - // machineHash: observationInput.machine - // ? getMachineHash(observationInput.machine) - // : undefined, - // } satisfies AgentObservation; - - // agent.send({ - // type: 'agent.observe', - // observation, - // }); - - // return observation; - // }; - // agent.getObservations = () => agent.getSnapshot().context.observations; - - // agent.addPlan = (plan) => { - // agent.send({ - // type: 'agent.plan', - // plan, - // }); - // }; - // agent.getPlans = () => agent.getSnapshot().context.plans; - - // agent.interact = ((actorRef, getInput) => { - // let prevState: ObservedState | undefined = undefined; - // let subscribed = true; - - // async function handleObservation(observationInput: AgentObservationInput) { - // const observation = agent.addObservation(observationInput); - - // const input = getInput?.(observation); - - // if (input) { - // await agentDecide(agent, { - // machine: actorRef.src as AnyStateMachine, - // state: observation.state, - // execute: async (event) => { - // actorRef.send(event); - // }, - // ...input, - // }); - // } - - // prevState = observationInput.state; - // } - - // // Inspect system, but only observe specified actor - // const sub = actorRef.system.inspect({ - // next: async (inspEvent) => { - // if ( - // !subscribed || - // inspEvent.actorRef !== actorRef || - // inspEvent.type !== '@xstate.snapshot' - // ) { - // return; - // } - - // const observationInput = { - // event: inspEvent.event, - // prevState, - // state: inspEvent.snapshot as any, - // machine: (actorRef as any).src, - // } satisfies AgentObservationInput; - - // await handleObservation(observationInput); - // }, - // }); - - // // If actor already started, interact with current state - // if ((actorRef as any)._processingStatus === 1) { - // handleObservation({ - // prevState: undefined, - // event: { type: '' }, // TODO: unknown events? - // state: actorRef.getSnapshot(), - // machine: (actorRef as any).src, - // }); - // } - - // return { - // unsubscribe: () => { - // sub.unsubscribe(); - // subscribed = false; - // }, - // }; - // }) as typeof agent.interact; - - // agent.observe = (actorRef) => { - // let prevState: ObservedState = actorRef.getSnapshot(); - - // const sub = actorRef.system.inspect({ - // next: async (inspEvent) => { - // if ( - // inspEvent.actorRef !== actorRef || - // inspEvent.type !== '@xstate.snapshot' - // ) { - // return; - // } - - // const observationInput = { - // event: inspEvent.event, - // prevState, - // state: inspEvent.snapshot as any, - // machine: (actorRef as any).src, - // } satisfies AgentObservationInput; - - // prevState = observationInput.state; - - // agent.addObservation(observationInput); - // }, - // }); - - // return sub; - // }; - - // agent.types = {} as any; - - // agent.wrap = (modelToWrap) => - // experimental_wrapLanguageModel({ - // model: modelToWrap, - // middleware: createAgentMiddleware(agent), - // }); - - // agent.model = experimental_wrapLanguageModel({ - // model, - // middleware: createAgentMiddleware(agent), - // }); - - // agent.start(); - - // return agent; -} - -export class Agent< - const TContextSchema extends ZodContextMapping, - const TEventSchemas extends ZodEventMapping, - TEvents extends EventObject = EventsFromZodEventMapping, - TContext = ContextFromZodContextMapping -> extends Actor> { - /** - * The name of the agent. All agents with the same name are related and - * able to share experiences (observations, feedback) with each other. - */ - public name?: string; - /** - * The unique identifier for the agent. - */ - public id: string; - public description?: string; - public events: TEventSchemas; - public context?: TContextSchema; - public planner?: AgentPlanner>; - public types: { - events: TEvents; - context: Compute; - }; - public model: LanguageModel; - public memory: AgentLongTermMemory | undefined; - public defaultOptions: any; // todo - - constructor({ - logic = agentLogic as AgentLogic, - id, - name, - description, - model, - events, - context, - planner = simplePlanner, - }: { - logic: AgentLogic; - id?: string; - name?: string; - description?: string; - model: GenerateTextOptions['model']; - events: TEventSchemas; - context?: TContextSchema; - planner?: AgentPlanner>; - }) { - super(logic); - this.model = model; - this.id = id ?? ''; - this.name = name; - this.description = description; - this.events = events; - this.context = context; - this.planner = planner; - this.types = {} as any; - - this.start(); - } - - /** - * Called whenever the agent (LLM assistant) receives or sends a message. - */ - public onMessage(fn: (message: AgentMessage) => void) { - return this.on('message', (ev) => fn(ev.message)); - } - - /** - * Retrieves messages from the agent's short-term (local) memory. - */ - public addMessage(messageInput: AgentMessageInput) { - const message = { - ...messageInput, - id: messageInput.id ?? randomId(), - timestamp: messageInput.timestamp ?? Date.now(), - sessionId: this.sessionId, - } satisfies AgentMessage; - this.send({ - type: 'agent.message', - message, - }); - - return message; - } - - public getMessages() { - return this.getSnapshot().context.messages; - } - - public addFeedback(feedbackInput: AgentFeedbackInput) { - const feedback = { - ...feedbackInput, - attributes: { ...feedbackInput.attributes }, - reward: feedbackInput.reward ?? 0, - timestamp: feedbackInput.timestamp ?? Date.now(), - sessionId: this.sessionId, - } satisfies AgentFeedback; - this.send({ - type: 'agent.feedback', - feedback, - }); - return feedback; - } - - /** - * Retrieves feedback from the agent's short-term (local) memory. - */ - public getFeedback() { - return this.getSnapshot().context.feedback; - } - - public addObservation( - observationInput: AgentObservationInput - ): AgentObservation { - const { prevState, event, state } = observationInput; - const observedState = { context: state.context, value: state.value }; - const observedPrevState = prevState - ? { - context: prevState.context, - value: prevState.value, - } - : undefined; - const observation = { - prevState: observedPrevState, - event, - state: observedState, - id: observationInput.id ?? randomId(), - sessionId: this.sessionId, - timestamp: observationInput.timestamp ?? Date.now(), - machineHash: observationInput.machine - ? getMachineHash(observationInput.machine) - : undefined, - } satisfies AgentObservation; - - this.send({ - type: 'agent.observe', - observation, - }); - - return observation; - } - - /** - * Retrieves observations from the agent's short-term (local) memory. - */ - public getObservations() { - return this.getSnapshot().context.observations; - } - - public addPlan(plan: AgentPlan) { - this.send({ - type: 'agent.plan', - plan, - }); - } - /** - * Retrieves strategies from the agent's short-term (local) memory. - */ - public getPlans() { - return this.getSnapshot().context.plans; - } - - /** - * Interacts with this state machine actor by inspecting state transitions and storing them as observations. - * - * Observations contain the `prevState`, `event`, and current `state` of this - * actor, as well as other properties that are useful when recalled. - * These observations are stored in the `agent`'s short-term (local) memory - * and can be retrieved via `agent.getObservations()`. - * - * @example - * ```ts - * // Only observes the actor's state transitions - * agent.interact(actor); - * - * actor.start(); - * ``` - */ - public interact(actorRef: TActor): Subscription; - /** - * Interacts with this state machine actor by: - * 1. Inspecting state transitions and storing them as observations - * 2. Deciding what to do next (which event to send the actor) based on - * the agent input returned from `getInput(observation)`, if `getInput(…)` is provided as the 2nd argument. - * - * Observations contain the `prevState`, `event`, and current `state` of this - * actor, as well as other properties that are useful when recalled. - * These observations are stored in the `agent`'s short-term (local) memory - * and can be retrieved via `agent.getObservations()`. - * - * @example - * ```ts - * // Observes the actor's state transitions and - * // makes a decision if on the "summarize" state - * agent.interact(actor, observed => { - * if (observed.state.matches('summarize')) { - * return { - * context: observed.state.context, - * goal: 'Summarize the message' - * } - * } - * }); - * - * actor.start(); - * ``` - */ - public interact( - actorRef: TActor, - getInput: ( - observation: AgentObservation - ) => AgentDecisionInput | undefined - ): Subscription; - public interact( - actorRef: TActor, - getInput?: ( - observation: AgentObservation - ) => AgentDecisionInput | undefined - ): Subscription { - let prevState: ObservedState | undefined = undefined; - let subscribed = true; - - const agent = this; - - async function handleObservation(observationInput: AgentObservationInput) { - const observation = agent.addObservation(observationInput); - - const input = getInput?.(observation); - - if (input) { - await agentDecide(agent, { - machine: actorRef.src as AnyStateMachine, - state: observation.state, - execute: async (event) => { - actorRef.send(event); - }, - ...input, - }); - } - - prevState = observationInput.state; - } - - // Inspect system, but only observe specified actor - const sub = actorRef.system.inspect({ - next: async (inspEvent) => { - if ( - !subscribed || - inspEvent.actorRef !== actorRef || - inspEvent.type !== '@xstate.snapshot' - ) { - return; - } - - const observationInput = { - event: inspEvent.event, - prevState, - state: inspEvent.snapshot as any, - machine: (actorRef as any).src, - } satisfies AgentObservationInput; - - await handleObservation(observationInput); - }, - }); - - // If actor already started, interact with current state - if ((actorRef as any)._processingStatus === 1) { - handleObservation({ - prevState: undefined, - event: { type: '' }, // TODO: unknown events? - state: actorRef.getSnapshot(), - machine: (actorRef as any).src, - }); - } - - return { - unsubscribe: () => { - sub.unsubscribe(); - subscribed = false; - }, - }; - } - - public observe(actorRef: TActor) { - let prevState: ObservedState = actorRef.getSnapshot(); - - const sub = actorRef.system.inspect({ - next: async (inspEvent) => { - if ( - inspEvent.actorRef !== actorRef || - inspEvent.type !== '@xstate.snapshot' - ) { - return; - } - - const observationInput = { - event: inspEvent.event, - prevState, - state: inspEvent.snapshot as any, - machine: (actorRef as any).src, - } satisfies AgentObservationInput; - - prevState = observationInput.state; - - this.addObservation(observationInput); - }, - }); - - return sub; - } - - public wrap(modelToWrap: LanguageModelV1) { - return experimental_wrapLanguageModel({ - model: modelToWrap, - middleware: createAgentMiddleware(this), - }); - } - - /** - * Resolves with an `AgentPlan` based on the information provided in the `options`, including: - * - * - The `goal` for the agent to achieve - * - The observed current `state` - * - The `machine` (e.g. a state machine) that specifies what can happen next - * - Additional `context` - */ - public decide(opts: AgentDecideOptions) { - return agentDecide(this, opts); - } -} diff --git a/src/ai-sdk/index.test.ts b/src/ai-sdk/index.test.ts new file mode 100644 index 0000000..23a573e --- /dev/null +++ b/src/ai-sdk/index.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAiSdkAdapter, createAiSdkDecisionAdapter } from './index.js'; + +describe('createAiSdkAdapter', () => { + test('resolves schema-less choices with a custom model resolver', async () => { + const seen: Array<{ model: unknown; prompt: unknown }> = []; + const adapter = createAiSdkDecisionAdapter({ + resolveModel: (model) => ({ providerResolved: model }) as never, + generateText: async (options) => { + seen.push({ + model: options.model, + prompt: options.prompt, + }); + + return { + output: 'billing', + } as never; + }, + }); + + const result = await adapter.decide({ + model: 'openai/gpt-5.4-nano', + prompt: 'Refund request for last month.', + options: { + billing: { description: 'Billing support' }, + general: { description: 'General support' }, + }, + }); + + expect(result).toEqual({ + choice: 'billing', + data: {}, + }); + expect(seen).toEqual([ + { + model: { providerResolved: 'openai/gpt-5.4-nano' }, + prompt: 'Refund request for last month.', + }, + ]); + }); + + test('returns structured decision payloads for schema-backed options', async () => { + const adapter = createAiSdkDecisionAdapter({ + generateText: async () => + ({ + output: { + decision: 'research', + data: { + query: 'latest cloudflare agents docs', + }, + reasoning: 'Need the newest API details.', + }, + }) as never, + }); + + const result = await adapter.decide({ + model: 'openai/gpt-5.4-nano', + prompt: 'Find the current Cloudflare Agents docs.', + reasoning: true, + options: { + research: { + description: 'Do external research first.', + schema: z.object({ + query: z.string(), + }), + }, + answer: { + description: 'Answer directly.', + }, + }, + }); + + expect(result).toEqual({ + choice: 'research', + data: { + query: 'latest cloudflare agents docs', + }, + reasoning: 'Need the newest API details.', + }); + }); + + test('creates a generation-only machine adapter', async () => { + const adapter = createAiSdkAdapter({ + generateText: async (options) => + ({ + text: `generated ${options.prompt}`, + }) as never, + }); + + await expect( + adapter.generateText?.({ + model: 'openai/gpt-5.4-nano', + messages: [], + prompt: 'reply', + }) + ).resolves.toBe('generated reply'); + expect('decide' in adapter).toBe(false); + }); +}); diff --git a/src/ai-sdk/index.ts b/src/ai-sdk/index.ts new file mode 100644 index 0000000..43b1bf8 --- /dev/null +++ b/src/ai-sdk/index.ts @@ -0,0 +1,155 @@ +import { generateText, Output } from 'ai'; +import { z } from 'zod'; +import type { AgentAdapter, DecideAdapter, StandardSchemaV1 } from '../types.js'; + +type AiSdkGenerateText = typeof generateText; +type AiSdkModel = Parameters[0]['model']; + +export interface CreateAiSdkAdapterOptions { + resolveModel?: (model: string) => AiSdkModel; + generateText?: AiSdkGenerateText; +} + +/** + * Create an adapter that uses the Vercel AI SDK for generative states. + * By default, model strings are passed straight through to the AI SDK. + * For provider helpers such as `openai(...)`, pass `resolveModel`. + */ +export function createAiSdkAdapter( + config: CreateAiSdkAdapterOptions = {} +): AgentAdapter { + const generate = config.generateText ?? generateText; + + return { + async generateText({ model, system, prompt, messages, tools, toolChoice, outputSchema }) { + const result = await generate({ + model: resolveModel(model ?? 'default', config.resolveModel), + system, + prompt, + messages: messages as any, + tools: tools as any, + toolChoice: toolChoice as any, + ...(outputSchema + ? { + output: Output.object({ + schema: toZodSchema(outputSchema), + }), + } + : {}), + }); + + const output = result as { output?: unknown; text?: string }; + return output.output ?? output.text ?? result; + }, + }; +} + +/** + * Create a decision helper adapter for decide(...) and classify(...). + */ +export function createAiSdkDecisionAdapter( + config: CreateAiSdkAdapterOptions = {} +): DecideAdapter { + const generate = config.generateText ?? generateText; + + return { + async decide({ model, prompt, options, reasoning }) { + const optionKeys = Object.keys(options); + const allSchemaLess = Object.values(options).every((option) => !option.schema); + + if (allSchemaLess && !reasoning) { + const optionDescriptions = Object.entries(options) + .map(([key, opt]) => `- ${key}: ${opt.description}`) + .join('\n'); + + const result = await generate({ + model: resolveModel(model, config.resolveModel), + system: `You must choose exactly one of the following options:\n${optionDescriptions}`, + prompt, + output: Output.choice({ + options: optionKeys, + }), + }); + + return { + choice: result.output, + data: {}, + }; + } + + const optionSchemas: z.ZodTypeAny[] = []; + for (const [key, opt] of Object.entries(options)) { + optionSchemas.push( + z.object({ + decision: z.literal(key), + data: opt.schema ? toZodSchema(opt.schema) : z.object({}), + ...(reasoning ? { reasoning: z.string() } : {}), + }) + ); + } + + const schemas = optionSchemas; + const schema = + schemas.length === 1 + ? schemas[0]! + : z.union(schemas as [z.ZodType, z.ZodType, ...z.ZodType[]]); + + const optionDescriptions = Object.entries(options) + .map(([key, opt]) => `- ${key}: ${opt.description}`) + .join('\n'); + + const systemPrompt = `You must choose exactly one of the following options:\n${optionDescriptions}\n\nRespond with structured output containing the chosen decision and any required data.`; + + const result = await generate({ + model: resolveModel(model, config.resolveModel), + system: systemPrompt, + prompt, + output: Output.object({ + schema, + }), + }); + + const obj = result.output as { + decision: string; + data: Record; + reasoning?: string; + }; + + return { + choice: obj.decision, + data: obj.data ?? {}, + reasoning: obj.reasoning, + }; + }, + }; +} + +/** + * Convert a StandardSchemaV1 to a zod schema. + * If it's already a zod schema, return as-is. + * Otherwise, fall back to z.record for basic compatibility. + */ +function toZodSchema(schema: StandardSchemaV1): z.ZodType { + // Check if it's already a zod schema (has _zod property in v4) + if ('_zod' in schema || '_def' in schema) { + return schema as unknown as z.ZodType; + } + // Fallback: accept any object + return z.record(z.string(), z.unknown()); +} + +/** + * Resolve a model string to an AI SDK model. + * Supports custom resolution when users prefer provider helpers such as + * `openai('gpt-5.4-nano')`. + */ +function resolveModel( + model: string, + resolver?: (model: string) => AiSdkModel +): AiSdkModel { + if (resolver) { + return resolver(model); + } + + return model as any; +} diff --git a/src/classify.ts b/src/classify.ts new file mode 100644 index 0000000..12f2c0e --- /dev/null +++ b/src/classify.ts @@ -0,0 +1,67 @@ +import { z } from 'zod'; +import { decide } from './decide.js'; +import type { + ClassifyOptions, + ClassifyResultFor, + StandardSchemaV1, +} from './types.js'; + +export async function classify< + const TCategories extends Record, +>( + options: ClassifyOptions +): Promise> { + const result = await decide({ + adapter: options.adapter, + model: options.model, + prompt: buildClassificationPrompt(options.prompt, options.examples), + options: options.into, + reasoning: options.reasoning, + }); + + return { + category: result.choice as keyof TCategories & string, + }; +} + +function buildClassificationPrompt( + prompt: string, + examples: Array<{ input: string; category: string }> | undefined +): string { + if (!examples?.length) { + return prompt; + } + + return [ + prompt, + '', + 'Examples:', + ...examples.map((example) => `${example.category}: ${example.input}`), + ].join('\n'); +} + +export function classifyResultSchema< + const TCategories extends Record, +>( + into: TCategories +): StandardSchemaV1> { + const categories = Object.keys(into); + if (categories.length === 0) { + throw new Error('classifyResultSchema requires at least one category'); + } + + const categorySchema = + categories.length === 1 + ? z.literal(categories[0]!) + : z.union( + categories.map((category) => z.literal(category)) as [ + z.ZodLiteral, + z.ZodLiteral, + ...z.ZodLiteral[], + ] + ); + + return z.object({ + category: categorySchema, + }) as unknown as StandardSchemaV1>; +} diff --git a/src/cloudflare/index.test.ts b/src/cloudflare/index.test.ts new file mode 100644 index 0000000..18b8e5f --- /dev/null +++ b/src/cloudflare/index.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test } from 'vitest'; +import { + createCloudflareAgentRunStore, + createDurableObjectRunStore, + type CloudflareAgentRunStoreState, +} from './index.js'; + +describe('cloudflare adapter', () => { + test('creates a Durable Object RunStore', async () => { + const storage = new Map(); + const store = createDurableObjectRunStore({ + async get(key) { + return storage.get(key) as never; + }, + async put(key, value) { + storage.set(key, value); + }, + }); + + await store.append('session-1', { type: 'start', at: 1 }); + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 1, + createdAt: 2, + snapshot: { + sessionId: 'session-1', + createdAt: 2, + value: 'done', + status: 'done', + context: {}, + messages: [], + input: {}, + }, + }); + + await expect(store.loadEvents('session-1')).resolves.toEqual([ + { + type: 'start', + at: 1, + sequence: 1, + }, + ]); + await expect(store.loadLatestSnapshot('session-1')).resolves.toEqual( + expect.objectContaining({ + afterSequence: 1, + }) + ); + }); + + test('creates a Cloudflare Agents state-backed RunStore', async () => { + let state: CloudflareAgentRunStoreState = { + sessions: {}, + }; + const store = createCloudflareAgentRunStore({ + getState: () => state, + setState: (nextState) => { + state = nextState; + }, + }); + + await store.append('session-1', { type: 'approve', at: 1 }); + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 1, + createdAt: 2, + snapshot: { + sessionId: 'session-1', + createdAt: 2, + value: 'done', + status: 'done', + context: {}, + messages: [], + input: {}, + }, + }); + + await expect(store.loadEvents('session-1')).resolves.toEqual([ + { + type: 'approve', + at: 1, + sequence: 1, + }, + ]); + await expect(store.loadLatestSnapshot('session-1')).resolves.toEqual( + expect.objectContaining({ + afterSequence: 1, + }) + ); + }); +}); diff --git a/src/cloudflare/index.ts b/src/cloudflare/index.ts new file mode 100644 index 0000000..008f0bc --- /dev/null +++ b/src/cloudflare/index.ts @@ -0,0 +1,147 @@ +import type { + AgentSnapshot, + JournalEvent, + JournalEventRecord, + PersistedSnapshot, + RunStore, +} from '../types.js'; + +export interface DurableObjectStorageLike { + get(key: string): Promise; + put(key: string, value: T): Promise; +} + +export interface DurableObjectStateLike { + storage: DurableObjectStorageLike; +} + +export function createDurableObjectRunStore( + storage: DurableObjectStorageLike +): RunStore { + return { + async append(sessionId, event) { + const key = journalKey(sessionId); + const current = (await storage.get(key)) ?? []; + const sequence = + current.length === 0 + ? 1 + : current[current.length - 1]!.sequence + 1; + + await storage.put(key, [...current, { ...event, sequence }]); + return { sequence }; + }, + + async loadEvents(sessionId, afterSequence = 0) { + const current = + (await storage.get[]>( + journalKey(sessionId) + )) ?? []; + + return current + .filter((event) => event.sequence > afterSequence) + .sort((a, b) => a.sequence - b.sequence); + }, + + async loadLatestSnapshot(sessionId) { + const snapshots = + (await storage.get[]>( + snapshotsKey(sessionId) + )) ?? []; + + return ( + [...snapshots].sort( + (a, b) => + a.afterSequence - b.afterSequence || a.createdAt - b.createdAt + ).at(-1) ?? null + ); + }, + + async saveSnapshot(snapshot) { + const key = snapshotsKey(snapshot.sessionId); + const current = + (await storage.get[]>(key)) ?? []; + + await storage.put(key, [...current, snapshot]); + }, + }; +} + +type SessionEntry = { + events: JournalEventRecord[]; + snapshot: PersistedSnapshot | null; +}; + +export type CloudflareAgentRunStoreState = { + sessions: Record; +}; + +export function createCloudflareAgentRunStore(options: { + getState: () => CloudflareAgentRunStoreState; + setState: ( + nextState: CloudflareAgentRunStoreState + ) => void | Promise; +}): RunStore { + return { + async append(sessionId, event) { + const currentState = options.getState(); + const currentSession = currentState.sessions[sessionId] ?? { + events: [], + snapshot: null, + }; + const sequence = currentSession.events.length + 1; + const nextSession: SessionEntry = { + ...currentSession, + events: [...currentSession.events, { ...event, sequence }], + }; + + await options.setState({ + ...currentState, + sessions: { + ...currentState.sessions, + [sessionId]: nextSession, + }, + }); + + return { sequence }; + }, + + async loadEvents(sessionId, afterSequence = 0) { + return ( + options.getState().sessions[sessionId]?.events.filter( + (event) => event.sequence > afterSequence + ) ?? [] + ); + }, + + async loadLatestSnapshot(sessionId) { + return options.getState().sessions[sessionId]?.snapshot ?? null; + }, + + async saveSnapshot(snapshot) { + const currentState = options.getState(); + const currentSession = currentState.sessions[snapshot.sessionId] ?? { + events: [], + snapshot: null, + }; + + await options.setState({ + ...currentState, + sessions: { + ...currentState.sessions, + [snapshot.sessionId]: { + ...currentSession, + snapshot, + }, + }, + }); + }, + }; +} + +function journalKey(sessionId: string): string { + return `sessions/${sessionId}/journal`; +} + +function snapshotsKey(sessionId: string): string { + return `sessions/${sessionId}/snapshots`; +} diff --git a/src/crewai-equivalents/content-creator-flow.test.ts b/src/crewai-equivalents/content-creator-flow.test.ts new file mode 100644 index 0000000..fd4ab29 --- /dev/null +++ b/src/crewai-equivalents/content-creator-flow.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from 'vitest'; +import { createContentCreatorFlowExample } from '../../examples/index.js'; + +describe('CrewAI content creator flow equivalent', () => { + test('routes a request and generates specialized content', async () => { + const machine = createContentCreatorFlowExample({ + routeRequest: async () => ({ route: 'linkedin' }), + createLinkedInPost: async (request) => ({ + title: 'LinkedIn launch post', + body: `LinkedIn: ${request}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'Announce our AI workflow launch in a short professional post.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + route: 'linkedin', + title: 'LinkedIn launch post', + body: + 'LinkedIn: Announce our AI workflow launch in a short professional post.', + }); + } + }); +}); diff --git a/src/crewai-equivalents/email-auto-responder-flow.test.ts b/src/crewai-equivalents/email-auto-responder-flow.test.ts new file mode 100644 index 0000000..accd299 --- /dev/null +++ b/src/crewai-equivalents/email-auto-responder-flow.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from 'vitest'; +import { runEmailAutoResponderFlowExample } from '../../examples/index.js'; + +describe('CrewAI email auto responder flow equivalent', () => { + test('processes new emails and restores the same durable snapshot', async () => { + const result = await runEmailAutoResponderFlowExample( + [ + { + id: 'email-1', + sender: 'buyer@example.com', + subject: 'Pricing question', + body: 'Can you send pricing details?', + }, + { + id: 'email-2', + sender: 'founder@example.com', + subject: 'Partnership', + body: 'Interested in discussing a partnership.', + }, + ], + { + createDraft: async (email) => ({ + draft: `Draft for ${email.subject}`, + }), + } + ); + + expect(result.snapshot).toEqual(result.restoredSnapshot); + expect(result.snapshot).toEqual( + expect.objectContaining({ + value: 'waiting', + status: 'pending', + }) + ); + expect(result.snapshot.context.processedIds).toEqual(['email-1', 'email-2']); + expect(result.snapshot.context.drafts).toEqual({ + 'email-1': 'Draft for Pricing question', + 'email-2': 'Draft for Partnership', + }); + }); +}); diff --git a/src/crewai-equivalents/lead-score-flow.test.ts b/src/crewai-equivalents/lead-score-flow.test.ts new file mode 100644 index 0000000..8462101 --- /dev/null +++ b/src/crewai-equivalents/lead-score-flow.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, test } from 'vitest'; +import { createLeadScoreFlowExample } from '../../examples/index.js'; + +describe('CrewAI lead score flow equivalent', () => { + test('supports human review before generating outreach emails', async () => { + const machine = createLeadScoreFlowExample({ + scoreLeads: async ({ leads, reviewNote }) => ({ + scoredLeads: leads.map((lead, index) => ({ + ...lead, + score: 100 - index * 10 - (reviewNote ? 3 : 0), + rationale: reviewNote ?? 'initial', + })), + }), + writeEmails: async (leads) => ({ + drafts: leads.map((lead) => ({ + leadId: lead.id, + draft: `Email for ${lead.company}`, + })), + }), + }); + + const initial = machine.getInitialState({ + leads: [ + { id: 'lead-1', company: 'Acme', contact: 'Ana' }, + { id: 'lead-2', company: 'Beta', contact: 'Ben' }, + { id: 'lead-3', company: 'Gamma', contact: 'Gia' }, + ], + }); + const firstPass = await machine.execute(initial); + expect(firstPass.status).toBe('pending'); + if (firstPass.status !== 'pending') { + return; + } + + const rescored = machine.transition(firstPass.state, { + type: 'review.requestChanges', + note: 'Prefer companies already asking for demos.', + }); + const secondPass = await machine.execute(rescored); + expect(secondPass.status).toBe('pending'); + if (secondPass.status !== 'pending') { + return; + } + + const approved = machine.transition(secondPass.state, { + type: 'review.approve', + }); + const finalResult = await machine.execute(approved); + + expect(finalResult.status).toBe('done'); + if (finalResult.status === 'done') { + expect(finalResult.output.reviewCount).toBe(2); + expect(finalResult.output.topLeads).toHaveLength(3); + expect(finalResult.output.emailDrafts).toEqual([ + { leadId: 'lead-1', draft: 'Email for Acme' }, + { leadId: 'lead-2', draft: 'Email for Beta' }, + { leadId: 'lead-3', draft: 'Email for Gamma' }, + ]); + } + }); +}); diff --git a/src/crewai-equivalents/meeting-assistant-flow.test.ts b/src/crewai-equivalents/meeting-assistant-flow.test.ts new file mode 100644 index 0000000..c9a87f6 --- /dev/null +++ b/src/crewai-equivalents/meeting-assistant-flow.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from 'vitest'; +import { createMeetingAssistantFlowExample } from '../../examples/index.js'; + +describe('CrewAI meeting assistant flow equivalent', () => { + test('fans one meeting summary into multiple side effects', async () => { + const machine = createMeetingAssistantFlowExample({ + extractTasks: async () => ({ + summary: 'Agreed on launch scope and follow-ups.', + tasks: [ + { title: 'Send launch checklist', owner: 'Ana' }, + { title: 'Prepare customer email', owner: 'Ben' }, + ], + }), + addTasksToTrello: async (tasks) => ({ + trelloCardIds: tasks.map((_, index) => `card-${index + 1}`), + }), + saveTasksToCsv: async () => ({ csvPath: 'new_tasks.csv' }), + sendSlackNotification: async () => ({ slackMessageId: 'slack-123' }), + }); + + const result = await machine.execute( + machine.getInitialState({ + notes: 'Meeting notes go here.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + summary: 'Agreed on launch scope and follow-ups.', + tasks: [ + { title: 'Send launch checklist', owner: 'Ana' }, + { title: 'Prepare customer email', owner: 'Ben' }, + ], + trelloCardIds: ['card-1', 'card-2'], + csvPath: 'new_tasks.csv', + slackMessageId: 'slack-123', + }); + } + }); +}); diff --git a/src/crewai-equivalents/self-evaluation-loop-flow.test.ts b/src/crewai-equivalents/self-evaluation-loop-flow.test.ts new file mode 100644 index 0000000..4bbb51d --- /dev/null +++ b/src/crewai-equivalents/self-evaluation-loop-flow.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'vitest'; +import { createSelfEvaluationLoopFlowExample } from '../../examples/index.js'; + +describe('CrewAI self evaluation loop equivalent', () => { + test('iterates until the generated post passes evaluation', async () => { + const attempts: string[] = []; + const machine = createSelfEvaluationLoopFlowExample({ + generatePost: async ({ feedback, attempt }) => { + const post = + attempt === 1 + ? 'A very long post with too much detail and maybe an emoji :)' + : `Refined post after: ${feedback}`; + attempts.push(post); + return { post }; + }, + evaluatePost: async (post) => + post.includes('Refined') + ? { valid: true, feedback: null } + : { + valid: false, + feedback: 'Shorten it and remove emoji-like punctuation.', + }, + }); + + const result = await machine.execute( + machine.getInitialState({ + topic: 'Flying cars', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output.valid).toBe(true); + expect(result.output.attempt).toBe(2); + expect(attempts).toHaveLength(2); + expect(result.output.post).toContain('Refined post after'); + } + }); +}); diff --git a/src/crewai-equivalents/write-a-book-flow.test.ts b/src/crewai-equivalents/write-a-book-flow.test.ts new file mode 100644 index 0000000..6ee2c5c --- /dev/null +++ b/src/crewai-equivalents/write-a-book-flow.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from 'vitest'; +import { createWriteABookFlowExample } from '../../examples/index.js'; + +describe('CrewAI write a book flow equivalent', () => { + test('outlines a book, writes chapters in parallel, and compiles a manuscript', async () => { + const machine = createWriteABookFlowExample({ + createOutline: async () => ({ + title: 'The Workflow Book', + chapters: [ + { title: 'Chapter 1', brief: 'Introduction' }, + { title: 'Chapter 2', brief: 'Execution' }, + ], + }), + writeChapter: async ({ title, brief }) => ({ + title, + content: `${title}: ${brief}`, + }), + compileManuscript: async ({ title, chapters }) => ({ + manuscript: [ + `# ${title}`, + ...chapters.map((chapter) => `## ${chapter.title}\n${chapter.content}`), + ].join('\n\n'), + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + topic: 'Workflow systems', + goal: 'Teach developers how to build durable AI workflows.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output.title).toBe('The Workflow Book'); + expect(result.output.outline).toHaveLength(2); + expect(result.output.chapters).toEqual([ + { title: 'Chapter 1', content: 'Chapter 1: Introduction' }, + { title: 'Chapter 2', content: 'Chapter 2: Execution' }, + ]); + expect(result.output.manuscript).toContain('# The Workflow Book'); + } + }); +}); diff --git a/src/decide.ts b/src/decide.ts new file mode 100644 index 0000000..10f62d7 --- /dev/null +++ b/src/decide.ts @@ -0,0 +1,87 @@ +import { z } from 'zod'; +import { validateSchemaSync } from './utils.js'; +import type { + DecideAdapter, + DecideOptions, + DecideResultFor, + StandardSchemaV1, +} from './types.js'; + +export async function decide< + const TOptions extends Record, +>( + options: DecideOptions +): Promise> { + const adapter = requireAdapter(options.adapter, 'decide()'); + const result = await adapter.decide({ + model: options.model, + prompt: options.prompt, + options: options.options, + reasoning: options.reasoning, + }); + + const chosen = options.options[result.choice]; + if (!chosen) { + throw new Error( + `Adapter returned unknown decision '${result.choice}' for model '${options.model}'` + ); + } + + const data = chosen.schema + ? validateSchemaSync(chosen.schema, result.data) + : {}; + + return { + choice: result.choice, + data, + reasoning: result.reasoning, + } as DecideResultFor; +} + +export function requireAdapter( + adapter: DecideAdapter | undefined, + label: string +): DecideAdapter { + if (!adapter) { + throw new Error(`No adapter configured for ${label}`); + } + + return adapter; +} + +export function decideResultSchema< + const TOptions extends Record, +>( + options: TOptions, + config: { reasoning?: boolean } = {} +): StandardSchemaV1> { + const schemas = Object.entries(options).map(([choice, option]) => + z.object({ + choice: z.literal(choice), + data: option.schema ? toZodSchema(option.schema) : z.object({}), + ...(config.reasoning ? { reasoning: z.string().optional() } : {}), + }) + ); + + if (schemas.length === 0) { + throw new Error('decideResultSchema requires at least one option'); + } + + return (schemas.length === 1 + ? schemas[0]! + : z.union( + schemas as unknown as [ + z.ZodTypeAny, + z.ZodTypeAny, + ...z.ZodTypeAny[], + ] + )) as unknown as StandardSchemaV1>; +} + +function toZodSchema(schema: StandardSchemaV1): z.ZodTypeAny { + if ('_zod' in schema || '_def' in schema) { + return schema as unknown as z.ZodTypeAny; + } + + return z.record(z.string(), z.unknown()); +} diff --git a/src/decision.test.ts b/src/decision.test.ts deleted file mode 100644 index 5a768b3..0000000 --- a/src/decision.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { test, expect } from 'vitest'; -import { createAgent, fromDecision } from '.'; -import { createActor, createMachine, waitFor } from 'xstate'; -import { z } from 'zod'; -import { LanguageModelV1CallOptions } from 'ai'; -import { dummyResponseValues, MockLanguageModelV1 } from './mockModel'; - -const doGenerate = async (params: LanguageModelV1CallOptions) => { - const keys = - params.mode.type === 'regular' ? params.mode.tools?.map((t) => t.name) : []; - - return { - ...dummyResponseValues, - finishReason: 'tool-calls', - toolCalls: [ - { - toolCallType: 'function', - toolCallId: 'call-1', - toolName: keys![0], - args: `{ "type": "${keys?.[0]}" }`, - }, - ], - } as any; -}; - -test('fromDecision() makes a decision', async () => { - const model = new MockLanguageModelV1({ - doGenerate, - }); - const agent = createAgent({ - name: 'test', - model, - events: { - doFirst: z.object({}), - doSecond: z.object({}), - }, - }); - - const machine = createMachine({ - initial: 'first', - states: { - first: { - invoke: { - src: fromDecision(agent), - }, - on: { - doFirst: 'second', - }, - }, - second: { - invoke: { - src: fromDecision(agent), - }, - on: { - doSecond: 'third', - }, - }, - third: {}, - }, - }); - - const actor = createActor(machine); - - actor.start(); - - await waitFor(actor, (s) => s.matches('third')); - - expect(actor.getSnapshot().value).toBe('third'); -}); - -test('interacts with an actor', async () => { - const model = new MockLanguageModelV1({ - doGenerate, - }); - const agent = createAgent({ - name: 'test', - model, - events: { - doFirst: z.object({}), - doSecond: z.object({}), - }, - }); - - const machine = createMachine({ - initial: 'first', - states: { - first: { - on: { - doFirst: 'second', - }, - }, - second: { - on: { - doSecond: 'third', - }, - }, - third: {}, - }, - }); - - const actor = createActor(machine); - - agent.interact(actor, () => ({ - goal: 'Some goal', - })); - - actor.start(); - - await waitFor(actor, (s) => s.matches('third')); - - expect(actor.getSnapshot().value).toBe('third'); -}); - -test('interacts with an actor (late interaction)', async () => { - const model = new MockLanguageModelV1({ - doGenerate, - }); - const agent = createAgent({ - name: 'test', - model, - events: { - doFirst: z.object({}), - doSecond: z.object({}), - }, - }); - - const machine = createMachine({ - initial: 'first', - states: { - first: { - on: { - doFirst: 'second', - }, - }, - second: { - on: { - doSecond: 'third', - }, - }, - third: {}, - }, - }); - - const actor = createActor(machine); - - actor.start(); - - agent.interact(actor, () => ({ - goal: 'Some goal', - })); - - await waitFor(actor, (s) => s.matches('third')); - - expect(actor.getSnapshot().value).toBe('third'); -}); diff --git a/src/decision.ts b/src/decision.ts deleted file mode 100644 index 7f93c94..0000000 --- a/src/decision.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { AnyActor, AnyMachineSnapshot, fromPromise } from 'xstate'; -import { - AnyAgent, - AgentDecideOptions, - AgentDecisionLogic, - AgentDecisionInput, - AgentPlanner, - AgentPlan, - EventsFromZodEventMapping, -} from './types'; -import { simplePlanner } from './planners/simplePlanner'; - -export async function agentDecide( - agent: T, - options: AgentDecideOptions -): Promise> | undefined> { - const resolvedOptions = { - ...agent.defaultOptions, - ...options, - }; - const { - planner = simplePlanner as AgentPlanner, - goal, - events = agent.events, - state, - machine, - model = agent.model, - ...otherPlanInput - } = resolvedOptions; - - const plan = await planner(agent, { - model, - goal, - events, - state, - machine, - ...otherPlanInput, - }); - - if (plan?.nextEvent) { - agent.addPlan(plan); - await resolvedOptions.execute?.(plan.nextEvent); - } - - return plan; -} - -export function fromDecision( - agent: AnyAgent, - defaultInput?: AgentDecisionInput -): AgentDecisionLogic { - return fromPromise(async ({ input, self }) => { - const parentRef = self._parent; - if (!parentRef) { - return; - } - - const snapshot = parentRef.getSnapshot() as AnyMachineSnapshot; - const inputObject = typeof input === 'string' ? { goal: input } : input; - const resolvedInput = { - ...defaultInput, - ...inputObject, - }; - const contextToInclude = - resolvedInput.context === true - ? // include entire context - parentRef.getSnapshot().context - : resolvedInput.context; - const state = { - value: snapshot.value, - context: contextToInclude, - }; - - const plan = await agentDecide(agent, { - machine: (parentRef as AnyActor).logic, - state, - execute: async (event) => { - parentRef.send(event); - }, - ...resolvedInput, - }); - - return plan; - }) as AgentDecisionLogic; -} diff --git a/src/examples.test.ts b/src/examples.test.ts new file mode 100644 index 0000000..4233fd1 --- /dev/null +++ b/src/examples.test.ts @@ -0,0 +1,1951 @@ +import { describe, expect, test } from 'vitest'; +import { z } from 'zod'; +import { restoreSession, startSession } from './index.js'; + +import { + createAiSdkExample, + createChatbotExample, + AgentNetworkDurableObject, + createCloudflareAgentRunStore, + createDurableObjectRunStore, + createAdapterExample, + createBranchingExample, + createClassifyExample, + createConditionalSubflowExample, + createCustomerServiceSimExample, + createDecideExample, + createChatbotMessagesExample, + createEmailExample, + createErrorRetryExample, + createHitlExample, + createPersistenceExample, + createPersistenceSessionHttpHandler, + createStreamingSessionHttpController, + createJokeExample, + createJugsExample, + createMapReduceExample, + createMultiAgentNetworkExample, + createNewspaperExample, + createNextAiSdkUiRoute, + createNextReviewRouteHandlers, + createNextStreamingRouteHandlers, + runPersistenceExample, + runPersistentMultiAgentNetworkExample, + runPersistentStreamingExample, + runPersistentSupervisorExample, + createPlanAndExecuteExample, + createRaffleExample, + createRagExample, + createReactAgentExample, + createRewooExample, + createReflectionExample, + createRiverCrossingExample, + createSimpleExample, + createSqlAgentExample, + createGuardrailedBugfixWorkflowExample, + createGuardrailedIncidentResponseExample, + createUnguardedIncidentResponseExample, + createSubflowExample, + createSupervisorExample, + createToolCallingExample, + createTutorExample, +} from '../examples/index.js'; + +function createSseReader(response: Response) { + const reader = response.body!.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + return { + async next(): Promise<{ event: string; data: unknown }> { + while (true) { + const match = buffer.match(/^event: ([^\n]+)\ndata: ([^\n]+)\n\n/); + if (match) { + buffer = buffer.slice(match[0].length); + return { + event: match[1]!, + data: JSON.parse(match[2]!), + }; + } + + const chunk = await reader.read(); + if (chunk.done) { + throw new Error('SSE stream closed before the next event was available.'); + } + + buffer += decoder.decode(chunk.value, { stream: true }); + } + }, + + async cancel() { + await reader.cancel(); + }, + }; +} + +describe('curated examples', () => { + test('simple example runs to a final output', async () => { + const machine = createSimpleExample({ + generateText: async () => ({ summary: 'A short summary.' }), + }); + const result = await machine.execute( + machine.getInitialState({ text: 'Longer source text.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ summary: 'A short summary.' }); + } + }); + + test('ai sdk example routes and drafts a structured reply', async () => { + const machine = createAiSdkExample({ + adapter: { + decide: async () => ({ + choice: 'billing', + data: { confidence: 0.93 }, + }), + }, + draftReply: async ({ route, confidence, message }) => ({ + subject: `${route.toUpperCase()} reply`, + body: `${message} :: ${confidence.toFixed(2)}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ message: 'Please refund invoice 123.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + route: 'billing', + confidence: 0.93, + subject: 'BILLING reply', + body: 'Please refund invoice 123. :: 0.93', + }); + } + }); + + test('chatbot messages example accumulates structured conversation turns', async () => { + const machine = createChatbotMessagesExample(async (messages) => ({ + message: { + role: 'assistant', + content: `Replying to: ${messages.at(-1)?.content ?? ''}`, + }, + })); + + const afterUserMessage = machine.transition(machine.getInitialState(), { + type: 'messages.user', + message: { + role: 'user', + content: 'Hello there', + }, + }); + const result = await machine.execute(afterUserMessage); + + expect(result.status).toBe('pending'); + if (result.status === 'pending') { + expect(result.messages).toEqual([ + { role: 'user', content: 'Hello there' }, + { role: 'assistant', content: 'Replying to: Hello there' }, + ]); + expect(result.context.finalMessage).toEqual({ + role: 'assistant', + content: 'Replying to: Hello there', + }); + } + }); + + test('rag example retrieves context and produces a grounded answer', async () => { + const machine = createRagExample({ + retrieve: async (question) => ({ + documents: [ + { id: 'doc-1', content: `${question} :: first fact` }, + { id: 'doc-2', content: `${question} :: second fact` }, + ], + }), + adapter: { + generateText: async ({ prompt }) => ({ + answer: String(prompt) + .replace('Question: ', '') + .replace('\n\nDocuments:\n- [doc-1] ', ' => ') + .replace('\n- [doc-2] ', ' | '), + }), + }, + }); + + const result = await machine.execute( + machine.getInitialState({ question: 'What is LangGraph?' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + question: 'What is LangGraph?', + documents: [ + { id: 'doc-1', content: 'What is LangGraph? :: first fact' }, + { id: 'doc-2', content: 'What is LangGraph? :: second fact' }, + ], + answer: + 'What is LangGraph? => What is LangGraph? :: first fact | What is LangGraph? :: second fact', + }); + } + }); + + test('persistence example restores a durable session to the same final snapshot', async () => { + const result = await runPersistenceExample( + { request: 'Approve the annual budget summary.' }, + { + summarize: async ({ request, approved }) => ({ + summary: `${request} :: approved=${String(approved)}`, + }), + } + ); + + expect(result.liveSnapshot).toEqual(result.restoredSnapshot); + expect(result.liveSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Approve the annual budget summary.', + approved: true, + summary: 'Approve the annual budget summary. :: approved=true', + }, + }) + ); + }); + + test('cloudflare durable object example store persists journal and snapshots', async () => { + const storage = new Map(); + const store = createDurableObjectRunStore({ + async get(key) { + return storage.get(key) as never; + }, + async put(key, value) { + storage.set(key, value); + }, + }); + + await store.append('session-1', { + type: 'xstate.init', + at: 1, + }); + await store.append('session-1', { + type: 'approve', + at: 2, + }); + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 2, + snapshot: { + value: 'done', + context: {}, + messages: [], + status: 'done', + createdAt: 2, + sessionId: 'session-1', + input: {}, + }, + createdAt: 2, + }); + + await expect(store.loadEvents('session-1')).resolves.toEqual([ + expect.objectContaining({ sequence: 1, type: 'xstate.init' }), + expect.objectContaining({ sequence: 2, type: 'approve' }), + ]); + await expect(store.loadLatestSnapshot('session-1')).resolves.toEqual( + expect.objectContaining({ + sessionId: 'session-1', + afterSequence: 2, + }) + ); + }); + + test('cloudflare agents example store persists durable sessions in synced state', async () => { + let state = { + sessions: {}, + }; + const store = createCloudflareAgentRunStore({ + getState: () => state, + setState: async (nextState) => { + state = nextState; + }, + }); + const machine = createPersistenceExample(async ({ request, approved }) => ({ + summary: `${request} :: approved=${String(approved)}`, + })); + + const run = await startSession(machine, { + store, + input: { + request: 'Approve the Cloudflare rollout.', + }, + }); + + await run.send({ type: 'approve' }); + + const restored = await restoreSession(machine, { + sessionId: run.sessionId, + store, + }); + + expect(restored.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Approve the Cloudflare rollout.', + approved: true, + summary: 'Approve the Cloudflare rollout. :: approved=true', + }, + }) + ); + expect(Object.keys(state.sessions)).toEqual([run.sessionId]); + }); + + test('http session example exposes start, send, and status over Request/Response', async () => { + const handle = createPersistenceSessionHttpHandler({ + summarize: async ({ request, approved }) => ({ + summary: `${request} :: approved=${String(approved)}`, + }), + }); + + const startResponse = await handle( + new Request('https://agent.test/sessions', { + method: 'POST', + body: JSON.stringify({ + request: 'Approve the annual budget summary.', + }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const startBody = await startResponse.json() as { + sessionId: string; + snapshot: { value: string; status: string }; + }; + + expect(startBody.snapshot).toEqual( + expect.objectContaining({ + value: 'review', + status: 'active', + }) + ); + + const sendResponse = await handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}/events`, { + method: 'POST', + body: JSON.stringify({ type: 'approve' }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const sendBody = await sendResponse.json() as { + snapshot: { + value: string; + status: string; + output: { + request: string; + approved: boolean; + summary: string; + }; + }; + }; + + expect(sendBody.snapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Approve the annual budget summary.', + approved: true, + summary: 'Approve the annual budget summary. :: approved=true', + }, + }) + ); + + const statusResponse = await handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}`, { + method: 'GET', + }) + ); + const statusBody = await statusResponse.json() as { + snapshot: { + value: string; + status: string; + output: { + request: string; + approved: boolean; + summary: string; + }; + }; + }; + + expect(statusBody.snapshot).toEqual(sendBody.snapshot); + }); + + test('http streaming session example reconnects with only new SSE parts after restore', async () => { + const controller = createStreamingSessionHttpController(); + + const startResponse = await controller.handle( + new Request('https://agent.test/sessions', { + method: 'POST', + body: JSON.stringify({ + streamId: 'stream-1', + text: 'hello', + }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const startBody = await startResponse.json() as { + sessionId: string; + }; + + const firstStreamResponse = await controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}/stream`) + ); + const firstReader = createSseReader(firstStreamResponse); + + controller.advance('stream-1'); + + await expect(firstReader.next()).resolves.toEqual({ + event: 'textPart', + data: { + type: 'textPart', + delta: 'hel', + }, + }); + + await firstReader.cancel(); + controller.dropActiveSession(startBody.sessionId); + + const secondStreamResponse = await controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}/stream`) + ); + const secondReader = createSseReader(secondStreamResponse); + + controller.advance('stream-1'); + + await expect(secondReader.next()).resolves.toEqual({ + event: 'textPart', + data: { + type: 'textPart', + delta: 'lo', + }, + }); + await expect(secondReader.next()).resolves.toEqual({ + event: 'done', + data: { + text: 'hello', + }, + }); + + const statusResponse = await controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}`) + ); + const statusBody = await statusResponse.json() as { + snapshot: { + value: string; + status: string; + output: { text: string }; + }; + }; + + expect(statusBody.snapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { text: 'hello' }, + }) + ); + }); + + test('next app router review example adapts Request/Response handlers to dynamic route params', async () => { + const routes = createNextReviewRouteHandlers({ + summarize: async ({ request, approved }) => ({ + summary: `${request} :: approved=${String(approved)}`, + }), + }); + + const startResponse = await routes.sessions.POST( + new Request('https://agent.test/api/agent', { + method: 'POST', + body: JSON.stringify({ + request: 'Approve the quarterly report.', + }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const startBody = await startResponse.json() as { + sessionId: string; + snapshot: { value: string; status: string }; + }; + + expect(startBody.snapshot).toEqual( + expect.objectContaining({ + value: 'review', + status: 'active', + }) + ); + + const sendResponse = await routes.events.POST( + new Request(`https://agent.test/api/agent/${startBody.sessionId}/events`, { + method: 'POST', + body: JSON.stringify({ type: 'approve' }), + headers: { + 'content-type': 'application/json', + }, + }), + { + params: Promise.resolve({ + sessionId: startBody.sessionId, + }), + } + ); + const sendBody = await sendResponse.json() as { + snapshot: { + value: string; + status: string; + output: { + request: string; + approved: boolean; + summary: string; + }; + }; + }; + + expect(sendBody.snapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Approve the quarterly report.', + approved: true, + summary: 'Approve the quarterly report. :: approved=true', + }, + }) + ); + }); + + test('next app router streaming example reconnects with only new streamed parts', async () => { + const routes = createNextStreamingRouteHandlers(); + + const startResponse = await routes.sessions.POST( + new Request('https://agent.test/api/agent', { + method: 'POST', + body: JSON.stringify({ + streamId: 'next-stream-1', + text: 'hello', + }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const startBody = await startResponse.json() as { sessionId: string }; + + const firstStreamResponse = await routes.stream.GET( + new Request(`https://agent.test/api/agent/${startBody.sessionId}/stream`), + { + params: Promise.resolve({ + sessionId: startBody.sessionId, + }), + } + ); + const firstReader = createSseReader(firstStreamResponse); + + routes.advance('next-stream-1'); + + await expect(firstReader.next()).resolves.toEqual({ + event: 'textPart', + data: { + type: 'textPart', + delta: 'hel', + }, + }); + + await firstReader.cancel(); + routes.dropActiveSession(startBody.sessionId); + + const secondStreamResponse = await routes.stream.GET( + new Request(`https://agent.test/api/agent/${startBody.sessionId}/stream`), + { + params: Promise.resolve({ + sessionId: startBody.sessionId, + }), + } + ); + const secondReader = createSseReader(secondStreamResponse); + + routes.advance('next-stream-1'); + + await expect(secondReader.next()).resolves.toEqual({ + event: 'textPart', + data: { + type: 'textPart', + delta: 'lo', + }, + }); + await expect(secondReader.next()).resolves.toEqual({ + event: 'done', + data: { + text: 'hello', + }, + }); + }); + + test('next app-shaped route files import cleanly', async () => { + const [ + routesModule, + chatRouteModule, + reviewSessionsRouteModule, + reviewSessionRouteModule, + reviewEventsRouteModule, + streamingSessionsRouteModule, + streamingSessionRouteModule, + streamingStreamRouteModule, + ] = await Promise.all([ + import(new URL('../examples/apps/next/lib/routes.ts', import.meta.url).href), + import(new URL('../examples/apps/next/app/api/chat/route.ts', import.meta.url).href), + import( + new URL('../examples/apps/next/app/api/review-sessions/route.ts', import.meta.url).href + ), + import( + new URL( + '../examples/apps/next/app/api/review-sessions/[sessionId]/route.ts', + import.meta.url + ).href + ), + import( + new URL( + '../examples/apps/next/app/api/review-sessions/[sessionId]/events/route.ts', + import.meta.url + ).href + ), + import( + new URL('../examples/apps/next/app/api/stream-sessions/route.ts', import.meta.url).href + ), + import( + new URL( + '../examples/apps/next/app/api/stream-sessions/[sessionId]/route.ts', + import.meta.url + ).href + ), + import( + new URL( + '../examples/apps/next/app/api/stream-sessions/[sessionId]/stream/route.ts', + import.meta.url + ).href + ), + ]); + + expect(routesModule.runtime).toBe('nodejs'); + expect(typeof routesModule.chatRoute.POST).toBe('function'); + expect(typeof routesModule.reviewRoutes.sessions.POST).toBe('function'); + expect(typeof routesModule.streamingRoutes.stream.GET).toBe('function'); + expect(typeof chatRouteModule.POST).toBe('function'); + expect(typeof reviewSessionsRouteModule.POST).toBe('function'); + expect(typeof reviewSessionRouteModule.GET).toBe('function'); + expect(typeof reviewEventsRouteModule.POST).toBe('function'); + expect(typeof streamingSessionsRouteModule.POST).toBe('function'); + expect(typeof streamingSessionRouteModule.GET).toBe('function'); + expect(typeof streamingStreamRouteModule.GET).toBe('function'); + }); + + test('next ai sdk ui route streams UI message parts from machine emissions', async () => { + const route = createNextAiSdkUiRoute({ + streamReply: async ({ messages, onDelta }) => { + expect(messages.at(-1)).toMatchObject({ + role: 'user', + }); + onDelta('Hel'); + onDelta('lo'); + return { text: 'Hello' }; + }, + }); + + const response = await route.POST( + new Request('https://agent.test/api/chat', { + method: 'POST', + body: JSON.stringify({ + messages: [ + { + id: 'user-1', + role: 'user', + parts: [ + { + type: 'text', + text: 'Say hello.', + }, + ], + }, + ], + }), + headers: { + 'content-type': 'application/json', + }, + }) + ); + const body = await response.text(); + + expect(response.headers.get('x-vercel-ai-ui-message-stream')).toBe('v1'); + expect(body).toContain('"type":"data-notification"'); + expect(body).toContain('"message":"Drafting reply..."'); + expect(body).toContain('"type":"source"'); + expect(body).toContain('"title":"Stately Agent documentation"'); + expect(body).toContain('"type":"text-start"'); + expect(body).toContain('"delta":"Hel"'); + expect(body).toContain('"delta":"lo"'); + expect(body).toContain('"type":"text-end"'); + }); + + test('cloudflare durable network example restores and settles a network run', async () => { + const storage = new Map(); + const firstInstance = new AgentNetworkDurableObject({ + storage: { + async get(key) { + return storage.get(key) as never; + }, + async put(key, value) { + storage.set(key, value); + }, + }, + }); + + const startResponse = await firstInstance.fetch( + new Request('https://example.com/start', { + method: 'POST', + body: JSON.stringify({ topic: 'durable networks' }), + }) + ); + const started = await startResponse.json() as { + sessionId: string; + snapshot: { status: string }; + }; + + const resumedInstance = new AgentNetworkDurableObject({ + storage: { + async get(key) { + return storage.get(key) as never; + }, + async put(key, value) { + storage.set(key, value); + }, + }, + }); + const resumeResponse = await resumedInstance.fetch( + new Request( + `https://example.com/resume?sessionId=${started.sessionId}`, + { method: 'POST' } + ) + ); + const resumed = await resumeResponse.json() as { + sessionId: string; + snapshot: { + status: string; + output: { + topic: string; + handoffs: string[]; + }; + }; + }; + + expect(started.sessionId).toBe(resumed.sessionId); + expect(resumed.snapshot.status).toBe('done'); + expect(resumed.snapshot.output).toEqual( + expect.objectContaining({ + topic: 'durable networks', + handoffs: [ + 'researcher:collect the strongest supporting facts', + 'writer:turn the current notes into a concise summary', + ], + }) + ); + }); + + test('hitl example exposes typed pending events', async () => { + const machine = createHitlExample(); + const result = await machine.execute( + machine.getInitialState({ task: 'Draft an answer' }) + ); + + expect(result.status).toBe('pending'); + if (result.status === 'pending') { + expect(result.value).toBe('gathering'); + expect(result.events['user.message']).toBeDefined(); + expect(result.events['user.approve']).toBeDefined(); + } + }); + + test('decide example chooses a branch and carries typed data', async () => { + const machine = createDecideExample({ + decide: async () => ({ + choice: 'askForClarification', + data: { question: 'Which order is affected?' }, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'The customer says their invoice is wrong.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + action: 'askForClarification', + payload: { question: 'Which order is affected?' }, + }); + } + }); + + test('classify example reduces to a category only', async () => { + const machine = createClassifyExample({ + decide: async () => ({ + choice: 'billing', + data: {}, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'I need help with a refund for my duplicate charge.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ category: 'billing' }); + } + }); + + test('adapter example uses the provided schema-aware adapter', async () => { + const machine = createAdapterExample({ + decide: async () => ({ + choice: 'billing', + data: { confidence: 0.9 }, + }), + }); + const result = await machine.execute(machine.getInitialState({ message: 'refund my last invoice' })); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + route: 'billing', + confidence: 0.9, + }); + } + }); + + test('error retry example recovers from transient invoke failures', async () => { + const machine = createErrorRetryExample(async ({ attempt }) => { + if (attempt === 1) { + throw new Error('temporary outage'); + } + + return { answer: 'Recovered answer.' }; + }); + + const result = await machine.execute( + machine.getInitialState({ question: 'Can this retry?' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + answer: 'Recovered answer.', + attempts: 2, + errors: ['temporary outage'], + }); + } + }); + + test('conditional subflow example routes directly into the requested child flow', async () => { + const machine = createConditionalSubflowExample({ + draft: async ({ topic, bullets }) => ({ + draft: `${topic}: ${bullets.join(', ')}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + topic: 'state machines', + mode: 'draft', + bullets: ['deterministic', 'resumable'], + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + mode: 'draft', + bullets: ['deterministic', 'resumable'], + draft: 'state machines: deterministic, resumable', + }); + } + }); + + test('persistent multi-agent network example restores from a mid-handoff snapshot', async () => { + let step = 0; + const result = await runPersistentMultiAgentNetworkExample( + { topic: 'resumable coordination' }, + { + adapter: { + decide: async () => { + step += 1; + + if (step === 1) { + return { + choice: 'research', + data: { focus: 'collect durable coordination notes' }, + }; + } + + if (step === 2) { + return { + choice: 'write', + data: { angle: 'produce the final coordination memo' }, + }; + } + + return { + choice: 'finalize', + data: {}, + }; + }, + }, + research: async ({ topic, focus }) => ({ + notes: [`${topic}:${focus}:a`, `${topic}:${focus}:b`], + }), + write: async ({ topic, notes, angle }) => ({ + draft: `${topic} | ${angle} | ${notes.join(' / ')}`, + }), + } + ); + + expect(result.restoredSnapshot).toEqual(result.liveSnapshot); + expect(result.liveSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + output: { + topic: 'resumable coordination', + notes: [ + 'resumable coordination:collect durable coordination notes:a', + 'resumable coordination:collect durable coordination notes:b', + ], + draft: + 'resumable coordination | produce the final coordination memo | resumable coordination:collect durable coordination notes:a / resumable coordination:collect durable coordination notes:b', + handoffs: [ + 'researcher:collect durable coordination notes', + 'writer:produce the final coordination memo', + ], + }, + }) + ); + }); + + test('persistent supervisor example restores from a persisted retry handoff', async () => { + let decisions = 0; + + const result = await runPersistentSupervisorExample( + { request: 'Reverse the duplicate subscription charge.' }, + { + adapter: { + decide: async () => { + decisions += 1; + + if (decisions === 1) { + return { + choice: 'retry', + data: { + instruction: 'Retry using the verified billing email on file.', + }, + }; + } + + return { + choice: 'escalate', + data: { + reason: 'Escalate to billing because the account is still ambiguous.', + }, + }; + }, + }, + handle: async ({ attempt, instruction }) => ({ + status: 'blocked' as const, + issue: + attempt === 1 + ? 'Missing account identifier.' + : `Still blocked after retry: ${instruction}`, + }), + maxAttempts: 2, + } + ); + + expect(result.liveSnapshot).toEqual(result.restoredSnapshot); + expect(result.liveSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Reverse the duplicate subscription charge.', + status: 'escalated', + resolution: null, + escalationReason: + 'Escalate to billing because the account is still ambiguous.', + attemptCount: 2, + history: [ + 'worker:1:blocked:Missing account identifier.', + 'supervisor:retry:Retry using the verified billing email on file.', + 'worker:2:blocked:Still blocked after retry: Retry using the verified billing email on file.', + 'supervisor:escalate:Escalate to billing because the account is still ambiguous.', + ], + }, + }) + ); + }); + + test('persistent streaming example resumes with only new live parts after restore', async () => { + const result = await runPersistentStreamingExample(); + + expect(result.initialParts).toEqual(['hel']); + expect(result.restoredParts).toEqual(['lo']); + expect(result.initialSnapshot).toEqual( + expect.objectContaining({ + value: 'writing', + status: 'active', + }) + ); + expect(result.restoredSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { text: 'hello' }, + }) + ); + expect(result.journal.map((event) => event.type)).toEqual([ + 'xstate.init', + 'xstate.done.invoke.writing', + ]); + }); + + test('branching example fans out plain async work and summarizes it', async () => { + const machine = createBranchingExample({ + analyzeDocs: async () => 'docs', + analyzeIssues: async () => 'issues', + analyzeCode: async () => 'code', + summarize: async ({ docs, issues, code }) => ({ + summary: `${docs}/${issues}/${code}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'agents' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + docs: 'docs', + issues: 'issues', + code: 'code', + summary: 'docs/issues/code', + }); + } + }); + + test('decide example uses structured payloads while classify does not', async () => { + const decideMachine = createDecideExample({ + decide: async () => ({ + choice: 'reply', + data: { message: 'Hello there' }, + }), + }); + const classifyMachine = createClassifyExample({ + decide: async () => ({ + choice: 'general', + data: {}, + }), + }); + + const decideResult = await decideMachine.execute( + decideMachine.getInitialState({ + request: 'Please answer this support question.', + }) + ); + const classifyResult = await classifyMachine.execute( + classifyMachine.getInitialState({ + request: 'This is a general support question.', + }) + ); + + expect(decideResult.status).toBe('done'); + expect(classifyResult.status).toBe('done'); + + if (decideResult.status === 'done' && classifyResult.status === 'done') { + expect(decideResult.output).toEqual({ + action: 'reply', + payload: { message: 'Hello there' }, + }); + expect(classifyResult.output).toEqual({ category: 'general' }); + } + }); + + test('hitl example event schemas validate payloads', async () => { + const machine = createHitlExample(); + const pending = await machine.execute( + machine.getInitialState({ task: 'Draft an answer' }) + ); + + expect(pending.status).toBe('pending'); + if (pending.status === 'pending') { + const validation = pending.events['user.message']!['~standard'].validate({ + type: 'user.message', + message: 'Here is the missing detail', + }); + + expect(validation.issues).toBeUndefined(); + } + }); + + test('decide example uses schemas on branch payloads', async () => { + const machine = createDecideExample({ + decide: async () => ({ + choice: 'reply', + data: { message: 'Resolved' }, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'Please respond to this support request.', + }) + ); + + expect(result.status).toBe('done'); + expect( + z + .object({ + action: z.string(), + payload: z.object({ message: z.string() }), + }) + .safeParse(result.status === 'done' ? result.output : null).success + ).toBe(true); + }); + + test('chatbot example accepts a user message and replies', async () => { + const machine = createChatbotExample({ + adapter: { + decide: async () => ({ choice: 'respond', data: {} }), + }, + reply: async () => ({ response: 'Assistant reply' }), + }); + + const pending = await machine.execute(machine.getInitialState()); + expect(pending.status).toBe('pending'); + + if (pending.status === 'pending') { + const next = machine.transition(pending.state, { + type: 'user.message', + message: 'Hello there', + }); + const result = await machine.execute(next); + + expect(result.status).toBe('pending'); + if (result.status === 'pending') { + expect(result.context.transcript).toEqual([ + 'User: Hello there', + 'Assistant: Assistant reply', + ]); + } + } + }); + + test('customer service sim example reaches a terminal outcome', async () => { + const machine = createCustomerServiceSimExample({ + serviceReply: async () => ({ response: 'We can help.' }), + customerReply: async () => ({ + response: 'Thanks, that works.', + done: true, + outcome: 'resolved', + }), + }); + + const result = await machine.execute( + machine.getInitialState({ issue: 'I want a refund.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + transcript: [ + 'Customer: I want a refund.', + 'Agent: We can help.', + 'Customer: Thanks, that works.', + ], + turnCount: 1, + outcome: 'resolved', + }); + } + }); + + test('email example can pause for clarification and then draft using tools', async () => { + let checkCount = 0; + const machine = createEmailExample({ + adapter: { + decide: async () => { + checkCount += 1; + return checkCount === 1 + ? { + choice: 'askForClarification', + data: { questions: ['Which day should I offer?'] }, + } + : { choice: 'draft', data: {} }; + }, + }, + tools: { + lookupContactName: async () => 'Pat Lee', + lookupAvailability: async () => ['Friday at 1 PM'], + createSignature: async (name) => `Best,\n${name}`, + }, + compose: async ({ + email, + instructions, + clarifications, + contactName, + availability, + signature, + }) => ({ + replyEmail: [ + `Hi ${contactName},`, + '', + `Thanks for your note: "${email}"`, + instructions, + clarifications.join(' '), + `I am available ${availability.join(' or ')}.`, + '', + signature, + ] + .filter(Boolean) + .join('\n'), + }), + }); + + const first = await machine.execute( + machine.getInitialState({ + email: 'Can you meet next week?', + instructions: 'Reply with one specific slot.', + }) + ); + + expect(first.status).toBe('pending'); + if (first.status === 'pending') { + expect(first.context.questions).toEqual(['Which day should I offer?']); + + const next = machine.transition(first.state, { + type: 'user.answer', + answer: 'Offer Friday afternoon.', + }); + const done = await machine.execute(next); + + expect(done.status).toBe('done'); + if (done.status === 'done') { + expect( + z + .object({ + replyEmail: z.string(), + clarifications: z.array(z.string()), + }) + .safeParse(done.output).success + ).toBe(true); + } + } + }); + + test('joke example produces a rating and acceptance flag', async () => { + const results = [ + { joke: 'A short joke about ducks.' }, + { rating: 9, explanation: 'It works.' }, + ]; + const machine = createJokeExample({ + generateText: async () => results.shift(), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'ducks' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + topic: 'ducks', + joke: 'A short joke about ducks.', + rating: 9, + explanation: 'It works.', + accepted: true, + }); + } + }); + + test('jugs example solves the 3 and 5 gallon puzzle', async () => { + const machine = createJugsExample(); + const result = await machine.execute(machine.getInitialState()); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + jug3: 3, + jug5: 4, + steps: [ + 'Filled the 5-gallon jug.', + 'Poured from the 5-gallon jug into the 3-gallon jug.', + 'Emptied the 3-gallon jug.', + 'Poured from the 5-gallon jug into the 3-gallon jug.', + 'Filled the 5-gallon jug.', + 'Poured from the 5-gallon jug into the 3-gallon jug.', + ], + reasoning: [ + 'Start by filling the larger jug.', + 'Transfer water into the 3-gallon jug.', + 'Empty the smaller jug to make room.', + 'Move the remaining water into the 3-gallon jug.', + 'Refill the 5-gallon jug.', + 'Top off the 3-gallon jug to leave 4 gallons.', + 'The 5-gallon jug now holds exactly 4 gallons.', + ], + }); + } + }); + + test('map-reduce example decomposes work items and reduces the result', async () => { + const machine = createMapReduceExample({ + planSubjects: async () => ({ + subjects: ['one', 'two'], + }), + writeJoke: async (subject) => `joke:${subject}`, + chooseBest: async (jokes) => ({ + bestJoke: jokes.at(-1) ?? '', + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'agents' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + subjects: ['one', 'two'], + jokes: ['joke:one', 'joke:two'], + bestJoke: 'joke:two', + }); + } + }); + + test('subflow example composes a child machine inside a parent workflow', async () => { + const machine = createSubflowExample({ + research: async (topic) => ({ + bullets: [`fact about ${topic}`, `detail about ${topic}`], + }), + write: async ({ topic, bullets }) => ({ + draft: `${topic}: ${bullets.join(' / ')}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'agents' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + bullets: ['fact about agents', 'detail about agents'], + draft: 'agents: fact about agents / detail about agents', + }); + } + }); + + test('multi-agent network example coordinates specialist handoffs through a supervisor state', async () => { + let step = 0; + + const machine = createMultiAgentNetworkExample({ + adapter: { + decide: async () => { + step += 1; + + if (step === 1) { + return { + choice: 'research', + data: { focus: 'collect technical notes' }, + }; + } + + if (step === 2) { + return { + choice: 'write', + data: { angle: 'produce a short memo' }, + }; + } + + return { + choice: 'finalize', + data: {}, + }; + }, + }, + research: async ({ topic, focus }) => ({ + notes: [`${topic}:${focus}:a`, `${topic}:${focus}:b`], + }), + write: async ({ topic, notes, angle }) => ({ + draft: `${topic} | ${angle} | ${notes.join(' / ')}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'durable agents' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + topic: 'durable agents', + notes: [ + 'durable agents:collect technical notes:a', + 'durable agents:collect technical notes:b', + ], + draft: + 'durable agents | produce a short memo | durable agents:collect technical notes:a / durable agents:collect technical notes:b', + handoffs: [ + 'researcher:collect technical notes', + 'writer:produce a short memo', + ], + }); + } + }); + + test('tool-calling example emits live tool activity and completes with output', async () => { + const machine = createToolCallingExample(async (city, emitProgress) => { + emitProgress({ + toolName: 'getWeather', + message: `Checking radar for ${city}`, + step: 1, + }); + emitProgress({ + toolName: 'getWeather', + message: `Preparing forecast for ${city}`, + step: 2, + }); + + return { + forecast: `Rainy in ${city}`, + }; + }); + + const { createMemoryRunStore, startSession } = await import('./index.js'); + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { city: 'New York' }, + }); + const events: string[] = []; + + run.on('toolCall', (event) => { + events.push(`call:${event.toolName}`); + }); + run.on('toolProgress', (event) => { + events.push(`progress:${event.toolName}:${event.step}`); + }); + run.on('toolResult', (event) => { + events.push(`result:${event.toolName}`); + }); + + await new Promise((resolve, reject) => { + run.onDone(() => resolve()); + run.onError((event) => reject(event.error)); + }); + + expect(events).toEqual([ + 'call:getWeather', + 'progress:getWeather:1', + 'progress:getWeather:2', + 'result:getWeather', + ]); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + output: { forecast: 'Rainy in New York' }, + }) + ); + }); + + test('sql-agent example retries after a bad query and then answers from rows', async () => { + let decisions = 0; + + const machine = createSqlAgentExample({ + adapter: { + decide: async () => { + decisions += 1; + + if (decisions === 1) { + return { + choice: 'query', + data: { + query: 'SELECT total FROM invoices WHERE customer = "Acme"', + }, + }; + } + + if (decisions === 2) { + return { + choice: 'query', + data: { + query: "SELECT customer, total FROM invoices WHERE customer = 'Acme'", + }, + }; + } + + return { + choice: 'answer', + data: { + answer: 'Acme has one invoice total of 42.', + }, + }; + }, + }, + executeQuery: async ({ query }) => { + if (query.includes('"Acme"')) { + return { + status: 'error' as const, + error: 'SQL syntax error near double quotes.', + }; + } + + return { + status: 'success' as const, + rows: [{ customer: 'Acme', total: 42 }], + }; + }, + }); + + const result = await machine.execute( + machine.getInitialState({ + question: 'What is Acme owed?', + schema: 'invoices(customer text, total integer)', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + question: 'What is Acme owed?', + schema: 'invoices(customer text, total integer)', + answer: 'Acme has one invoice total of 42.', + latestRows: [{ customer: 'Acme', total: 42 }], + latestError: null, + queryHistory: [ + 'SELECT total FROM invoices WHERE customer = "Acme"', + "SELECT customer, total FROM invoices WHERE customer = 'Acme'", + ], + }); + } + }); + + test('react agent example loops through a tool and returns a final answer', async () => { + const { createMemoryRunStore, startSession } = await import('./index.js'); + const agent = createReactAgentExample({ + search: async (query) => `result for ${query}`, + model: async ({ messages }) => { + const last = messages.at(-1); + + if (!last || last.role === 'user') { + return { + kind: 'tool' as const, + toolName: 'search', + input: { query: 'weather in sf' }, + message: 'Searching for weather in sf', + }; + } + + if (last.role === 'tool') { + return { + kind: 'final' as const, + message: `I found: ${last.content}`, + }; + } + + return { + kind: 'final' as const, + message: 'I could not complete the request.', + }; + }, + }); + const run = await startSession(agent, { + store: createMemoryRunStore(), + input: { + messages: [{ role: 'user', content: 'weather in sf' }], + }, + }); + const events: string[] = []; + + run.on('toolCall', (event) => { + events.push(`call:${event.toolName}`); + }); + run.on('toolResult', (event) => { + events.push(`result:${event.toolName}`); + }); + + await new Promise((resolve, reject) => { + run.onDone(() => resolve()); + run.onError((event) => reject(event.error)); + }); + + expect(events).toEqual(['call:search', 'result:search']); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + output: expect.objectContaining({ + finalMessage: 'I found: result for weather in sf', + }), + }) + ); + }); + + test('rewoo example plans named steps, executes them with references, and solves the objective', async () => { + const machine = createRewooExample({ + plan: async () => ({ + steps: [ + { + id: 'E1', + instruction: 'Collect a fact', + input: 'LangGraphJS', + }, + { + id: 'E2', + instruction: 'Summarize the fact', + input: 'Use #E1 in one concise sentence', + }, + ], + }), + executeStep: async ({ step, resolvedInput }) => ({ + result: `${step.id}:${resolvedInput}`, + }), + solve: async ({ resultsById }) => ({ + answer: `${resultsById.E1} | ${resultsById.E2}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ objective: 'understand the repo' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + objective: 'understand the repo', + steps: [ + { + id: 'E1', + instruction: 'Collect a fact', + input: 'LangGraphJS', + }, + { + id: 'E2', + instruction: 'Summarize the fact', + input: 'Use #E1 in one concise sentence', + }, + ], + resultsById: { + E1: 'E1:LangGraphJS', + E2: 'E2:Use E1:LangGraphJS in one concise sentence', + }, + answer: 'E1:LangGraphJS | E2:Use E1:LangGraphJS in one concise sentence', + }); + } + }); + + test('supervisor example retries a blocked worker and can still resolve the request', async () => { + let decisions = 0; + + const machine = createSupervisorExample({ + adapter: { + decide: async () => { + decisions += 1; + + return { + choice: decisions === 1 ? 'retry' : 'escalate', + data: + decisions === 1 + ? { instruction: 'Retry using the customer email on file.' } + : { reason: 'Escalate to billing.' }, + }; + }, + }, + handle: async ({ attempt, instruction }) => + attempt === 1 + ? { + status: 'blocked' as const, + issue: 'Missing account identifier.', + } + : { + status: 'resolved' as const, + response: `Resolved after retry: ${instruction}`, + }, + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'Fix the duplicate subscription charge.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + request: 'Fix the duplicate subscription charge.', + status: 'resolved', + resolution: 'Resolved after retry: Retry using the customer email on file.', + escalationReason: null, + attemptCount: 2, + history: [ + 'worker:1:blocked:Missing account identifier.', + 'supervisor:retry:Retry using the customer email on file.', + 'worker:2:resolved:Resolved after retry: Retry using the customer email on file.', + ], + }); + } + }); + + test('newspaper example loops through critique and revision', async () => { + const machine = createNewspaperExample({ + search: async () => ({ searchResults: ['a', 'b', 'c'] }), + curate: async () => ({ searchResults: ['a', 'b'] }), + write: async () => ({ article: 'Draft article' }), + critique: async (_article, revisionCount) => ({ + critique: revisionCount === 0 ? 'Tighten the ending.' : null, + }), + revise: async (article, critique) => ({ + article: `${article} Revised: ${critique}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'Robotics' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + topic: 'Robotics', + article: 'Draft article Revised: Tighten the ending.', + revisionCount: 1, + searchResults: ['a', 'b'], + }); + } + }); + + test('plan-and-execute example creates a plan, executes steps, and synthesizes', async () => { + const machine = createPlanAndExecuteExample({ + plan: async () => ({ + plan: ['one', 'two'], + }), + executeStep: async ({ step }) => ({ + result: `result:${step}`, + }), + synthesize: async ({ stepResults }) => ({ + answer: stepResults.join(' + '), + }), + }); + + const result = await machine.execute( + machine.getInitialState({ goal: 'ship a feature' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + goal: 'ship a feature', + plan: ['one', 'two'], + stepResults: ['result:one', 'result:two'], + answer: 'result:one + result:two', + }); + } + }); + + test('raffle example collects entries and reports a winner', async () => { + const machine = createRaffleExample(async (entries) => ({ + winningEntry: entries[1] ?? '', + firstRunnerUp: entries[0] ?? '', + secondRunnerUp: entries[2] ?? '', + explanation: 'Selected the second entry for the demo.', + })); + + const pending = await machine.execute(machine.getInitialState()); + expect(pending.status).toBe('pending'); + + if (pending.status === 'pending') { + let state = machine.transition(pending.state, { + type: 'user.entry', + entry: 'TypeScript', + }); + state = machine.transition(state, { + type: 'user.entry', + entry: 'Rust', + }); + state = machine.transition(state, { + type: 'user.entry', + entry: 'Go', + }); + state = machine.transition(state, { type: 'user.draw' }); + + const result = await machine.execute(state); + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + entries: ['TypeScript', 'Rust', 'Go'], + winner: 'Rust', + firstRunnerUp: 'TypeScript', + secondRunnerUp: 'Go', + explanation: 'Selected the second entry for the demo.', + }); + } + } + }); + + test('reflection example loops through critique and revision until ready', async () => { + const machine = createReflectionExample({ + draft: async () => ({ + draft: 'Initial draft', + }), + reflect: async ({ revisionCount }) => ({ + feedback: revisionCount === 0 ? 'Clarify the main point.' : null, + }), + revise: async ({ draft, feedback }) => ({ + draft: `${draft} Revised: ${feedback}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ task: 'Explain event sourcing simply.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + task: 'Explain event sourcing simply.', + draft: 'Initial draft Revised: Clarify the main point.', + feedback: null, + revisionCount: 1, + }); + } + }); + + test('river crossing example moves every item safely to the right bank', async () => { + const machine = createRiverCrossingExample(); + const result = await machine.execute(machine.getInitialState()); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + leftBank: [], + rightBank: ['cabbage', 'goat', 'wolf'], + steps: [ + 'The farmer took the goat across the river.', + 'The farmer crossed the river alone.', + 'The farmer took the wolf across the river.', + 'The farmer took the goat across the river.', + 'The farmer took the cabbage across the river.', + 'The farmer crossed the river alone.', + 'The farmer took the goat across the river.', + ], + reasoning: [ + 'Move the goat first so it is not left with the cabbage.', + 'Return alone to ferry another item.', + 'Take the wolf across while the goat waits safely alone.', + 'Bring the goat back so the wolf is not left with it.', + 'Take the cabbage across now that the goat is with you.', + 'Return alone to fetch the goat.', + 'Bring the goat across to complete the crossing.', + 'Everyone is safely across.', + ], + }); + } + }); + + test('tutor example gives feedback and a response', async () => { + const machine = createTutorExample({ + teach: async () => ({ instruction: 'Use a more complete sentence.' }), + respond: async () => ({ response: 'Claro, puedo ayudarte.' }), + }); + + const result = await machine.execute( + machine.getInitialState({ message: 'Yo necesito ayuda' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + conversation: [ + 'User: Yo necesito ayuda', + 'Tutor: Claro, puedo ayudarte.', + ], + feedback: 'Use a more complete sentence.', + response: 'Claro, puedo ayudarte.', + }); + } + }); +}); + +describe('guardrailed workflow examples', () => { + test('bugfix example exposes per-state prompts and tools', () => { + const machine = createGuardrailedBugfixWorkflowExample(); + + const planning = machine.getInitialState({ task: 'Fix divide().' }); + expect(planning.value).toBe('planning'); + expect(Object.keys(planning.tools ?? {})).toEqual( + expect.arrayContaining([ + 'Read', + 'Grep', + 'Glob', + 'LS', + 'Bash', + ]) + ); + + expect(Object.keys(planning.tools ?? {})).not.toContain('Edit'); + }); + + test('incident response example withholds destructive tools', async () => { + const machine = createGuardrailedIncidentResponseExample(); + + const diagnose = machine.getInitialState({}); + expect(diagnose.value).toBe('diagnosing'); + expect(Object.keys(diagnose.tools ?? {})).toContain('get_logs'); + expect(Object.keys(diagnose.tools ?? {})).not.toContain('delete_volume'); + + const result = await machine.execute(diagnose); + expect(result.status).toBe('pending'); + if (result.status !== 'pending') { + throw new Error('Expected approval state'); + } + + expect(result.state.value).toBe('awaitingApproval'); + expect(Object.keys(result.state.tools ?? {})).toEqual( + expect.arrayContaining(['Read', 'event.APPROVED', 'event.REJECTED']) + ); + }); + + test('unguarded incident response example exposes every API action', () => { + const machine = createUnguardedIncidentResponseExample(); + const state = machine.getInitialState({}); + + expect(state.value).toBe('working'); + expect(Object.keys(state.tools ?? {})).toContain('delete_volume'); + expect(Object.keys(state.tools ?? {})).toContain('restart_service'); + }); +}); diff --git a/src/fixtures/converter-machine.ts b/src/fixtures/converter-machine.ts new file mode 100644 index 0000000..47cfb05 --- /dev/null +++ b/src/fixtures/converter-machine.ts @@ -0,0 +1,68 @@ +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +declare function unknownTransition(): { target: 'done' }; + +export const namedMachine = createFixtureMachine('named-converter-machine'); + +export const warningMachine = createAgentMachine({ + id: 'warning-converter-machine', + schemas: { + events: { + go: z.object({ + type: z.literal('go'), + }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + go: () => unknownTransition(), + }, + }, + done: { + type: 'final', + }, + }, +}); + +export default createFixtureMachine('default-converter-machine'); + +export function createFixtureMachine(id = 'factory-converter-machine') { + return createAgentMachine({ + id, + schemas: { + events: { + submit: z.object({ + type: z.literal('submit'), + ok: z.boolean(), + }), + }, + }, + context: () => ({ + approved: false, + }), + initial: 'idle', + states: { + idle: { + on: { + submit: ({ event }) => + event.ok + ? { + target: 'done', + context: { approved: true }, + } + : { target: 'rejected' }, + }, + }, + rejected: { + type: 'final', + }, + done: { + type: 'final', + }, + }, + }); +} diff --git a/src/graph/index.test.ts b/src/graph/index.test.ts new file mode 100644 index 0000000..d0b3fa6 --- /dev/null +++ b/src/graph/index.test.ts @@ -0,0 +1,440 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; +import { analyzeGraph, toGraph, toMermaid } from './index.js'; + +declare function unknownTransition(): { target: 'done' }; + +test('exports finite states and transition edges as Stately graph JSON', () => { + const machine = createAgentMachine({ + id: 'graph-export', + schemas: { + events: { + submit: z.object({ + type: z.literal('submit'), + count: z.number(), + }), + }, + }, + context: () => ({ + total: 0, + }), + initial: 'idle', + states: { + idle: { + on: { + submit: ({ event }) => { + if (event.count > 0) { + return { + target: 'working', + context: { total: event.count }, + input: { index: event.count }, + }; + } + + return { + target: 'done', + }; + }, + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }), output: z.object({ + ok: z.boolean(), + }) }, + invoke: async () => ({ ok: true }), + onDone: () => ({ + target: 'done', + }), + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + expect(toGraph(machine)).toEqual({ + id: 'graph-export', + type: 'directed', + initialNodeId: 'idle', + data: undefined, + nodes: [ + { type: 'node', id: 'idle', label: 'idle', data: { type: 'state' } }, + { type: 'node', id: 'working', label: 'working', data: { type: 'state' } }, + { type: 'node', id: 'done', label: 'done', data: { type: 'final' } }, + ], + edges: [ + { + type: 'edge', + id: 'idle:submit:0', + sourceId: 'idle', + targetId: 'working', + label: 'submit [event.count > 0]', + data: { + event: 'submit', + source: 'event', + guard: { type: 'event.count > 0' }, + actions: { + context: true, + input: true, + }, + }, + }, + { + type: 'edge', + id: 'idle:submit:1', + sourceId: 'idle', + targetId: 'done', + label: 'submit [!(event.count > 0)]', + data: { + event: 'submit', + source: 'event', + guard: { type: '!(event.count > 0)' }, + }, + }, + { + type: 'edge', + id: 'working:done.invoke.working:2', + sourceId: 'working', + targetId: 'done', + label: 'done.invoke.working', + data: { + event: 'done.invoke.working', + source: 'invoke.done', + }, + }, + ], + }); +}); + +test('exports always transitions and message updates', () => { + const machine = createAgentMachine({ + id: 'always-graph', + context: () => ({}), + initial: 'checking', + states: { + checking: { + always: ({ messages }) => ({ + target: 'done', + messages: messages.concat({ role: 'assistant', content: 'ok' }), + }), + }, + done: { + type: 'final', + }, + }, + }); + + expect(toGraph(machine).edges).toEqual([ + { + type: 'edge', + id: 'checking::0', + sourceId: 'checking', + targetId: 'done', + label: 'always', + data: { + event: '', + source: 'always', + actions: { + messages: true, + }, + }, + }, + ]); +}); + +test('infers switch, early-return, and helper-call transition branches', () => { + const machine = createAgentMachine({ + id: 'ast-rich-export', + schemas: { + events: { + route: z.object({ + type: z.literal('route'), + kind: z.enum(['a', 'b', 'c']), + urgent: z.boolean(), + }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + route: ({ event }) => { + const toA = () => ({ target: 'a' as const }); + + if (event.urgent) { + return toA(); + } + + switch (event.kind) { + case 'b': + return { target: 'b' as const }; + case 'c': + return { target: 'c' as const }; + default: + return { target: 'fallback' as const }; + } + }, + }, + }, + a: { type: 'final' }, + b: { type: 'final' }, + c: { type: 'final' }, + fallback: { type: 'final' }, + }, + }); + + expect(toGraph(machine).edges).toEqual([ + expect.objectContaining({ + targetId: 'a', + data: expect.objectContaining({ + guard: { type: 'event.urgent' }, + }), + }), + expect.objectContaining({ + targetId: 'b', + data: expect.objectContaining({ + guard: { type: '(!(event.urgent)) && (event.kind === "b")' }, + }), + }), + expect.objectContaining({ + targetId: 'c', + data: expect.objectContaining({ + guard: { type: '(!(event.urgent)) && (event.kind === "c")' }, + }), + }), + expect.objectContaining({ + targetId: 'fallback', + data: expect.objectContaining({ + guard: { + type: '(!(event.urgent)) && (!(event.kind === "b") && !(event.kind === "c"))', + }, + }), + }), + ]); +}); + +test('reports graph warnings for unsupported transition analysis', () => { + const machine = createAgentMachine({ + id: 'ast-warning-export', + schemas: { + events: { + go: z.object({ type: z.literal('go') }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + go: () => { + return unknownTransition(); + }, + }, + }, + done: { type: 'final' }, + }, + }); + + expect(analyzeGraph(machine).warnings).toEqual([ + { + state: 'idle', + event: 'go', + message: + 'Unsupported helper call: unknownTransition() is not statically resolvable.', + }, + ]); + expect(toGraph(machine).data).toBeUndefined(); +}); + +test('resolves simple helper calls with arguments in guards and targets', () => { + const machine = createAgentMachine({ + id: 'helper-args-export', + schemas: { + events: { + choose: z.object({ + type: z.literal('choose'), + kind: z.enum(['approved', 'rejected']), + }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + choose: ({ event }) => { + function goTo( + target: 'approved' | 'rejected', + reason: string + ) { + return { + target, + context: { reason }, + }; + } + + return event.kind === 'approved' + ? goTo('approved', 'explicit approval path') + : goTo('rejected', 'explicit rejection path'); + }, + }, + }, + approved: { type: 'final' }, + rejected: { type: 'final' }, + }, + }); + + expect(toGraph(machine).edges).toEqual([ + expect.objectContaining({ + targetId: 'approved', + data: expect.objectContaining({ + guard: { type: 'event.kind === "approved"' }, + actions: { context: true }, + }), + }), + expect.objectContaining({ + targetId: 'rejected', + data: expect.objectContaining({ + guard: { type: '!(event.kind === "approved")' }, + actions: { context: true }, + }), + }), + ]); +}); + +test('resolves one-level helper forwarding with substituted arguments', () => { + const machine = createAgentMachine({ + id: 'helper-forwarding-export', + schemas: { + events: { + choose: z.object({ + type: z.literal('choose'), + kind: z.enum(['approved', 'rejected']), + }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + choose: ({ event }) => { + function goTo( + target: 'approved' | 'rejected', + reason: string + ) { + return { + target, + context: { reason }, + }; + } + + function route(kind: 'approved' | 'rejected') { + return goTo(kind, `routed:${kind}`); + } + + return event.kind === 'approved' + ? route('approved') + : route('rejected'); + }, + }, + }, + approved: { type: 'final' }, + rejected: { type: 'final' }, + }, + }); + + expect(toGraph(machine).edges).toEqual([ + expect.objectContaining({ + targetId: 'approved', + data: expect.objectContaining({ + guard: { type: 'event.kind === "approved"' }, + actions: { context: true }, + }), + }), + expect.objectContaining({ + targetId: 'rejected', + data: expect.objectContaining({ + guard: { type: '!(event.kind === "approved")' }, + actions: { context: true }, + }), + }), + ]); +}); + +test('exports a mermaid state diagram from the Stately graph data', () => { + const machine = createAgentMachine({ + id: 'mermaid-export', + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + finish: { target: 'done' }, + }, + }, + done: { + type: 'final', + }, + }, + }); + + expect(toMermaid(machine)).toBe(`stateDiagram-v2 + [*] --> idle + idle --> done : finish + done --> [*]`); +}); + +test('infers guards from conditional-expression transition branches', () => { + const machine = createAgentMachine({ + id: 'conditional-export', + schemas: { + events: { + choose: z.object({ + type: z.literal('choose'), + ok: z.boolean(), + }), + }, + }, + context: () => ({}), + initial: 'idle', + states: { + idle: { + on: { + choose: ({ event }) => + event.ok + ? { target: 'accepted' } + : { target: 'rejected' }, + }, + }, + accepted: { + type: 'final', + }, + rejected: { + type: 'final', + }, + }, + }); + + expect(toGraph(machine).edges).toEqual([ + expect.objectContaining({ + sourceId: 'idle', + targetId: 'accepted', + data: expect.objectContaining({ + guard: { type: 'event.ok' }, + }), + }), + expect.objectContaining({ + sourceId: 'idle', + targetId: 'rejected', + data: expect.objectContaining({ + guard: { type: '!(event.ok)' }, + }), + }), + ]); +}); diff --git a/src/graph/index.ts b/src/graph/index.ts new file mode 100644 index 0000000..f9b4cab --- /dev/null +++ b/src/graph/index.ts @@ -0,0 +1,1039 @@ +import { + createGraph, + type EdgeConfig, + type Graph as StatelyGraph, + type GraphEdge as StatelyGraphEdge, + type GraphNode as StatelyGraphNode, + type NodeConfig, +} from '@statelyai/graph'; +import { + toMermaidState, + type MermaidStateGraph, + type StateEdgeData, + type StateGraphData, + type StateNodeData, +} from '@statelyai/graph/mermaid'; +import ts from 'typescript'; +import type { + AgentMachine, + MachineConfig, + StateConfig, + TransitionResult, +} from '../types.js'; + +export interface AgentGraphNodeData { + type: 'state' | 'choice' | 'final'; +} + +export interface AgentGraphEdgeData { + event?: string; + source?: 'event' | 'invoke.done' | 'always'; + guard?: { + type: string; + }; + actions?: { + context?: boolean; + input?: boolean; + messages?: boolean; + }; +} + +export interface AgentGraphData { +} + +export interface AgentGraphWarning { + state: string; + event: string; + message: string; +} + +export interface AgentGraphAnalysis { + graph: AgentGraph; + warnings: AgentGraphWarning[]; +} + +export interface AgentGraph + extends StatelyGraph {} +export interface AgentGraphNode + extends StatelyGraphNode {} +export interface AgentGraphEdge + extends StatelyGraphEdge {} + +type InternalMachine = AgentMachine & { + __config?: MachineConfig; +}; + +type EdgeCandidate = { + target: string; + guard?: string; + hasContext?: boolean; + hasInput?: boolean; + hasMessages?: boolean; +}; + +type AnalysisResult = { + candidates: EdgeCandidate[]; + warnings: string[]; +}; + +type BlockAnalysis = AnalysisResult & { + exits: boolean; +}; + +type AnalyzableFunction = + | ts.ArrowFunction + | ts.FunctionExpression + | ts.FunctionDeclaration; + +type HelperMap = Map; +type BindingMap = Map; +const printer = ts.createPrinter({ removeComments: true }); + +/** + * Convert an agent machine to a Stately graph-compatible plain JSON object. + * + * Finite states come directly from the authored `states` object. Edges are + * inferred from static transition objects and transition handler ASTs. + */ +export function toGraph(machine: AgentMachine): AgentGraph { + return analyzeGraph(machine).graph; +} + +export function analyzeGraph(machine: AgentMachine): AgentGraphAnalysis { + const config = (machine as InternalMachine).__config; + if (!config) { + throw new Error('Machine config metadata is unavailable for graph export'); + } + + const nodes: Array> = Object.entries( + config.states + ).map(([id, state]) => ({ + id, + label: id, + data: { + type: getNodeType(state as StateConfig), + }, + })); + + const edges: Array> = []; + const warnings: AgentGraphWarning[] = []; + for (const [sourceId, state] of Object.entries(config.states)) { + const stateConfig = state as StateConfig; + + if (stateConfig.onDone) { + const event = `done.invoke.${sourceId}`; + const result = getTransitionEdges({ + sourceId, + event, + source: 'invoke.done', + transition: stateConfig.onDone, + ordinalOffset: edges.length, + }); + edges.push(...result.edges); + warnings.push(...formatWarnings(sourceId, event, result.warnings)); + } + + if (stateConfig.always) { + const event = ''; + const result = getTransitionEdges({ + sourceId, + event, + source: 'always', + transition: stateConfig.always, + ordinalOffset: edges.length, + }); + edges.push(...result.edges); + warnings.push(...formatWarnings(sourceId, 'always', result.warnings)); + } + + if (!stateConfig.on) { + continue; + } + + for (const [event, transition] of Object.entries(stateConfig.on)) { + const result = getTransitionEdges({ + sourceId, + event, + source: 'event', + transition, + ordinalOffset: edges.length, + }); + edges.push(...result.edges); + warnings.push(...formatWarnings(sourceId, event, result.warnings)); + } + } + + const graph = createGraph({ + id: machine.id, + initialNodeId: + typeof config.initial === 'string' ? config.initial : undefined, + nodes, + edges, + }); + + return { + graph, + warnings, + }; +} + +export function toMermaid(machine: AgentMachine): string { + return toMermaidState(toMermaidStateGraph(toGraph(machine))); +} + +function getNodeType(state: StateConfig): AgentGraphNodeData['type'] { + if (state.type === 'final') { + return 'final'; + } + + if (state.type === 'choice') { + return 'choice'; + } + + return 'state'; +} + +function toMermaidStateGraph(graph: AgentGraph): MermaidStateGraph { + const nodes: Array> = graph.nodes.map((node) => ({ + id: node.id, + label: node.label, + data: { + ...(node.data.type === 'choice' ? { stateType: 'choice' as const } : {}), + }, + })); + + const edges: Array> = graph.edges.map((edge) => ({ + id: edge.id, + sourceId: edge.sourceId, + targetId: edge.targetId, + label: edge.label ?? undefined, + data: {}, + })); + + if (graph.initialNodeId) { + const startId = `${graph.id}.__start`; + nodes.push({ + id: startId, + data: { isStart: true }, + }); + edges.unshift({ + id: `${startId}:initial`, + sourceId: startId, + targetId: graph.initialNodeId, + data: {}, + }); + } + + for (const node of graph.nodes) { + if (node.data.type !== 'final') { + continue; + } + + const endId = `${node.id}.__end`; + nodes.push({ + id: endId, + data: { isEnd: true }, + }); + edges.push({ + id: `${node.id}:final`, + sourceId: node.id, + targetId: endId, + data: {}, + }); + } + + return createGraph({ + id: graph.id, + type: graph.type, + initialNodeId: graph.initialNodeId ?? undefined, + data: { + diagramType: 'stateDiagram', + }, + nodes, + edges, + }); +} + +function getTransitionEdges(args: { + sourceId: string; + event: string; + source: NonNullable; + transition: unknown; + ordinalOffset: number; +}): { + edges: Array>; + warnings: string[]; +} { + const result = + typeof args.transition === 'function' + ? analyzeTransitionFunction(args.transition) + : analyzeTransitionObject(args.transition); + + return { + edges: result.candidates.map((candidate, index) => ({ + id: `${args.sourceId}:${args.event}:${args.ordinalOffset + index}`, + sourceId: args.sourceId, + targetId: candidate.target, + label: getEdgeLabel(args.event, candidate.guard), + data: { + event: args.event, + source: args.source, + ...(candidate.guard + ? { + guard: { + type: candidate.guard, + }, + } + : {}), + ...((candidate.hasContext || candidate.hasInput || candidate.hasMessages) + ? { + actions: { + ...(candidate.hasContext ? { context: true } : {}), + ...(candidate.hasInput ? { input: true } : {}), + ...(candidate.hasMessages ? { messages: true } : {}), + }, + } + : {}), + }, + })), + warnings: result.warnings, + }; +} + +function analyzeTransitionObject(transition: unknown): AnalysisResult { + const target = + transition && typeof transition === 'object' + ? (transition as TransitionResult).target + : undefined; + + if ( + transition + && typeof transition === 'object' + && 'target' in transition + && typeof target === 'string' + ) { + return { + candidates: [{ + target, + hasContext: 'context' in transition, + hasInput: 'input' in transition, + hasMessages: 'messages' in transition, + }], + warnings: [], + }; + } + + return { candidates: [], warnings: [] }; +} + +function analyzeTransitionFunction(fn: Function): AnalysisResult { + const source = fn + .toString() + .replace(/__name\([^)]*\);?/g, ''); + const file = ts.createSourceFile( + 'transition.ts', + `const __transition = ${source};`, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS + ); + const transitionFunction = findTransitionFunction(file); + + if (!transitionFunction) { + return { + candidates: [], + warnings: ['Unable to parse transition function.'], + }; + } + + const helpers = collectHelpers(transitionFunction); + + if (ts.isArrowFunction(transitionFunction) && !ts.isBlock(transitionFunction.body)) { + return analyzeTransitionExpression( + transitionFunction.body, + [], + file, + helpers, + new Map() + ); + } + + if (transitionFunction.body && ts.isBlock(transitionFunction.body)) { + return analyzeStatements( + transitionFunction.body.statements, + [], + file, + helpers, + new Map() + ); + } + + return { + candidates: [], + warnings: ['Unsupported transition function body.'], + }; +} + +function findTransitionFunction(file: ts.SourceFile): AnalyzableFunction | undefined { + let transitionFunction: AnalyzableFunction | undefined; + + function visit(node: ts.Node) { + if ( + ts.isVariableDeclaration(node) + && ts.isIdentifier(node.name) + && node.name.text === '__transition' + && node.initializer + && isAnalyzableFunction(node.initializer) + ) { + transitionFunction = node.initializer; + return; + } + + if (!transitionFunction) { + ts.forEachChild(node, visit); + } + } + + visit(file); + return transitionFunction; +} + +function isAnalyzableFunction(node: ts.Node): node is AnalyzableFunction { + return ( + ts.isArrowFunction(node) + || ts.isFunctionExpression(node) + || ts.isFunctionDeclaration(node) + ); +} + +function collectHelpers(fn: AnalyzableFunction): HelperMap { + const helpers: HelperMap = new Map(); + if (!fn.body || !ts.isBlock(fn.body)) { + return helpers; + } + + function visit(node: ts.Node) { + if ( + node !== fn.body + && isAnalyzableFunction(node) + ) { + return; + } + + if (ts.isFunctionDeclaration(node) && node.name && node.body) { + helpers.set(node.name.text, node); + return; + } + + if (ts.isVariableDeclaration(node)) { + if (!ts.isIdentifier(node.name) || !node.initializer) { + return; + } + + const initializer = unwrapParenthesized(node.initializer); + if ( + isAnalyzableFunction(initializer) + || ts.isObjectLiteralExpression(initializer) + || ts.isConditionalExpression(initializer) + ) { + helpers.set(node.name.text, initializer); + } + } + + ts.forEachChild(node, visit); + } + + visit(fn.body); + + return helpers; +} + +function analyzeStatements( + statements: ts.NodeArray, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): BlockAnalysis { + const candidates: EdgeCandidate[] = []; + const warnings: string[] = []; + const fallthroughGuards = [...guards]; + + for (const statement of statements) { + const result = analyzeStatement( + statement, + fallthroughGuards, + file, + helpers, + bindings + ); + candidates.push(...result.candidates); + warnings.push(...result.warnings); + + if (result.exits) { + return { candidates, warnings, exits: true }; + } + + if ( + ts.isIfStatement(statement) + && isReturnOnlyBranch(statement.thenStatement) + && !statement.elseStatement + ) { + fallthroughGuards.push( + `!(${renderExpressionText(statement.expression, file, bindings)})` + ); + } + } + + return { candidates, warnings, exits: false }; +} + +function analyzeStatement( + statement: ts.Statement, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): BlockAnalysis { + if (ts.isReturnStatement(statement)) { + if (!statement.expression) { + return { + candidates: [], + warnings: ['Return statement has no transition object.'], + exits: true, + }; + } + + const result = analyzeTransitionExpression( + statement.expression, + guards, + file, + helpers, + bindings + ); + + return { + candidates: result.candidates, + warnings: + result.candidates.length === 0 && result.warnings.length === 0 + ? [ + `Unsupported transition return expression: ${statement.expression.getText(file)}`, + ] + : result.warnings, + exits: true, + }; + } + + if (ts.isIfStatement(statement)) { + return analyzeIfStatement(statement, guards, file, helpers, bindings); + } + + if (ts.isSwitchStatement(statement)) { + return analyzeSwitchStatement(statement, guards, file, helpers, bindings); + } + + if ( + ts.isVariableStatement(statement) + || ts.isFunctionDeclaration(statement) + || ts.isEmptyStatement(statement) + ) { + return { candidates: [], warnings: [], exits: false }; + } + + return { + candidates: [], + warnings: [`Unsupported transition statement: ${statement.getText(file)}`], + exits: false, + }; +} + +function analyzeIfStatement( + statement: ts.IfStatement, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): BlockAnalysis { + const condition = renderExpressionText(statement.expression, file, bindings); + const thenResult = analyzeBranch( + statement.thenStatement, + [...guards, condition], + file, + helpers, + bindings + ); + const elseResult = statement.elseStatement + ? analyzeBranch( + statement.elseStatement, + [...guards, `!(${condition})`], + file, + helpers, + bindings + ) + : emptyBlockAnalysis(); + + return { + candidates: [...thenResult.candidates, ...elseResult.candidates], + warnings: [...thenResult.warnings, ...elseResult.warnings], + exits: thenResult.exits && !!statement.elseStatement && elseResult.exits, + }; +} + +function analyzeSwitchStatement( + statement: ts.SwitchStatement, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): BlockAnalysis { + const candidates: EdgeCandidate[] = []; + const warnings: string[] = []; + const expression = renderExpressionText(statement.expression, file, bindings); + const caseGuards: string[] = []; + let allClausesExit = statement.caseBlock.clauses.length > 0; + + for (const clause of statement.caseBlock.clauses) { + const clauseGuard = ts.isCaseClause(clause) + ? `${expression} === ${clause.expression.getText(file)}` + : caseGuards.length > 0 + ? caseGuards.map((guard) => `!(${guard})`).join(' && ') + : undefined; + + if (clauseGuard) { + caseGuards.push(clauseGuard); + } + + const result = analyzeStatements( + clause.statements, + clauseGuard ? [...guards, clauseGuard] : guards, + file, + helpers, + bindings + ); + candidates.push(...result.candidates); + warnings.push(...result.warnings); + allClausesExit = allClausesExit && result.exits; + } + + return { + candidates, + warnings, + exits: allClausesExit, + }; +} + +function analyzeBranch( + statement: ts.Statement, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): BlockAnalysis { + if (ts.isBlock(statement)) { + return analyzeStatements(statement.statements, guards, file, helpers, bindings); + } + + return analyzeStatement(statement, guards, file, helpers, bindings); +} + +function emptyBlockAnalysis(): BlockAnalysis { + return { + candidates: [], + warnings: [], + exits: false, + }; +} + +function isReturnOnlyBranch(statement: ts.Statement): boolean { + if (ts.isReturnStatement(statement)) { + return true; + } + + return ( + ts.isBlock(statement) + && statement.statements.length === 1 + && !!statement.statements[0] + && ts.isReturnStatement(statement.statements[0]) + ); +} + +function analyzeTransitionExpression( + expression: ts.Expression, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): AnalysisResult { + const current = unwrapParenthesized(expression); + + if (ts.isConditionalExpression(current)) { + const condition = renderExpressionText(current.condition, file, bindings); + + return mergeAnalysis([ + analyzeTransitionExpression( + current.whenTrue, + [...guards, condition], + file, + helpers, + bindings + ), + analyzeTransitionExpression( + current.whenFalse, + [...guards, `!(${condition})`], + file, + helpers, + bindings + ), + ]); + } + + if ( + ts.isCallExpression(current) + && ts.isIdentifier(current.expression) + ) { + const fallbackHelper = findHelperByName(file, current.expression.text); + const helper = + helpers.get(current.expression.text) + ?? fallbackHelper; + if (!helper) { + return { + candidates: [], + warnings: [ + `Unsupported helper call: ${current.expression.text}(${current.arguments.map((arg) => renderExpressionText(arg, file, bindings)).join(', ')}) is not statically resolvable.`, + ], + }; + } + + return analyzeHelper( + helper, + current.arguments, + guards, + file, + helpers, + bindings + ); + } + + const object = unwrapParenthesizedObject(current); + const target = object + ? getStringProperty(object, 'target', file, bindings) + : undefined; + if (!target) { + return { candidates: [], warnings: [] }; + } + + return { + candidates: [{ + target, + guard: combineGuardList(guards), + hasContext: object ? hasProperty(object, 'context') : false, + hasInput: object ? hasProperty(object, 'input') : false, + hasMessages: object ? hasProperty(object, 'messages') : false, + }], + warnings: [], + }; +} + +function analyzeHelper( + helper: AnalyzableFunction | ts.Expression, + args: ts.NodeArray, + guards: string[], + file: ts.SourceFile, + helpers: HelperMap, + bindings: BindingMap +): AnalysisResult { + if (isAnalyzableFunction(helper)) { + const helperBindings = createBindings(helper, args, bindings); + if (!helperBindings) { + return { + candidates: [], + warnings: [ + `Unsupported helper call: argument count for ${getHelperName(helper)}(...) could not be matched.`, + ], + }; + } + + if (ts.isArrowFunction(helper) && !ts.isBlock(helper.body)) { + return analyzeTransitionExpression( + helper.body, + guards, + file, + helpers, + helperBindings + ); + } + + if (helper.body && ts.isBlock(helper.body)) { + const result = analyzeStatements( + helper.body.statements, + guards, + file, + helpers, + helperBindings + ); + return { + candidates: result.candidates, + warnings: result.warnings, + }; + } + } + + if (ts.isExpression(helper)) { + if (args.length > 0) { + return { + candidates: [], + warnings: ['Unsupported helper call: non-function helper cannot accept arguments.'], + }; + } + + return analyzeTransitionExpression(helper, guards, file, helpers, bindings); + } + + return { + candidates: [], + warnings: ['Unsupported helper body.'], + }; +} + +function mergeAnalysis(results: AnalysisResult[]): AnalysisResult { + return { + candidates: results.flatMap((result) => result.candidates), + warnings: results.flatMap((result) => result.warnings), + }; +} + +function unwrapParenthesized(expression: T): ts.Expression { + let current: ts.Expression = expression; + + while (ts.isParenthesizedExpression(current)) { + current = current.expression; + } + + return current; +} + +function findHelperByName( + file: ts.SourceFile, + name: string +): AnalyzableFunction | ts.Expression | undefined { + let helper: AnalyzableFunction | ts.Expression | undefined; + + function visit(node: ts.Node) { + if (helper) { + return; + } + + if ( + ts.isFunctionDeclaration(node) + && node.name?.text === name + && node.body + ) { + helper = node; + return; + } + + if (ts.isVariableDeclaration(node)) { + if (!ts.isIdentifier(node.name) || node.name.text !== name || !node.initializer) { + ts.forEachChild(node, visit); + return; + } + + const initializer = unwrapParenthesized(node.initializer); + if ( + isAnalyzableFunction(initializer) + || ts.isObjectLiteralExpression(initializer) + || ts.isConditionalExpression(initializer) + ) { + helper = initializer; + return; + } + } + + ts.forEachChild(node, visit); + } + + visit(file); + return helper; +} + +function unwrapParenthesizedObject( + expression: ts.Expression +): ts.ObjectLiteralExpression | undefined { + let current = expression; + + while (ts.isParenthesizedExpression(current)) { + current = current.expression; + } + + return ts.isObjectLiteralExpression(current) ? current : undefined; +} + +function getStringProperty( + object: ts.ObjectLiteralExpression, + name: string, + file: ts.SourceFile, + bindings: BindingMap +): string | undefined { + const property = object.properties.find((candidate) => { + return ( + (ts.isPropertyAssignment(candidate) + || ts.isShorthandPropertyAssignment(candidate)) + && ts.isIdentifier(candidate.name) + && candidate.name.text === name + ); + }); + + if (!property) { + return undefined; + } + + if (ts.isPropertyAssignment(property)) { + return resolveStringExpression(property.initializer, file, bindings); + } + + if (ts.isShorthandPropertyAssignment(property)) { + const binding = bindings.get(property.name.text); + if (!binding) { + return property.name.text; + } + + return resolveStringExpression(binding, file, bindings); + } + + return undefined; +} + +function hasProperty(object: ts.ObjectLiteralExpression, name: string): boolean { + return object.properties.some((candidate) => { + return ( + (ts.isPropertyAssignment(candidate) + || ts.isShorthandPropertyAssignment(candidate)) + && ts.isIdentifier(candidate.name) + && candidate.name.text === name + ); + }); +} + +function getEdgeLabel(event: string, guard: string | undefined): string { + const label = event || 'always'; + if (!guard) { + return label; + } + + return `${label} [${guard}]`; +} + +function createBindings( + helper: AnalyzableFunction, + args: ts.NodeArray, + parentBindings: BindingMap +): BindingMap | null { + if (args.length > helper.parameters.length) { + return null; + } + + const bindings = new Map(parentBindings); + helper.parameters.forEach((parameter, index) => { + if (!ts.isIdentifier(parameter.name)) { + return; + } + + const arg = args[index]; + if (arg) { + bindings.set(parameter.name.text, substituteExpression(arg, parentBindings)); + } + }); + + return bindings; +} + +function getHelperName(helper: AnalyzableFunction): string { + if (helper.name) { + return helper.name.text; + } + + return 'helper'; +} + +function resolveStringExpression( + expression: ts.Expression, + file: ts.SourceFile, + bindings: BindingMap +): string | undefined { + const current = substituteExpression(unwrapParenthesized(expression), bindings); + + if (ts.isStringLiteralLike(current)) { + return current.text; + } + + if (ts.isNoSubstitutionTemplateLiteral(current)) { + return current.text; + } + + if (ts.isIdentifier(current)) { + return current.text; + } + + const rendered = renderExpressionText(current, file, bindings); + return /^["'`](.*)["'`]$/s.test(rendered) + ? rendered.slice(1, -1) + : undefined; +} + +function renderExpressionText( + expression: ts.Expression, + file: ts.SourceFile, + bindings: BindingMap +): string { + const substituted = substituteExpression(unwrapParenthesized(expression), bindings); + return printer.printNode(ts.EmitHint.Unspecified, substituted, file); +} + +function substituteExpression( + expression: ts.Expression, + bindings: BindingMap +): ts.Expression { + if (bindings.size === 0) { + return expression; + } + + const transformed = ts.transform(expression, [ + (context) => { + const visit: ts.Visitor = (node) => { + if (ts.isIdentifier(node) && bindings.has(node.text)) { + return substituteExpression(bindings.get(node.text)!, bindings); + } + + return ts.visitEachChild(node, visit, context); + }; + + return (node) => ts.visitNode(node, visit) as ts.Expression; + }, + ]); + + const substituted = transformed.transformed[0] as ts.Expression; + transformed.dispose(); + return substituted; +} + +function combineGuardList(guards: string[]): string | undefined { + if (guards.length === 0) { + return undefined; + } + + return guards + .map((guard) => guards.length === 1 ? guard : `(${guard})`) + .join(' && '); +} + +function formatWarnings( + state: string, + event: string, + warnings: string[] +): AgentGraphWarning[] { + return warnings.map((message) => ({ + state, + event, + message, + })); +} diff --git a/src/http/index.test.ts b/src/http/index.test.ts new file mode 100644 index 0000000..5357153 --- /dev/null +++ b/src/http/index.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; +import { createSessionHttpController } from './index.js'; + +function createSseReader(response: Response) { + const reader = response.body!.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + return { + async next(): Promise<{ event: string; data: unknown }> { + while (true) { + const match = buffer.match(/^event: ([^\n]+)\ndata: ([^\n]+)\n\n/); + if (match) { + buffer = buffer.slice(match[0].length); + return { + event: match[1]!, + data: JSON.parse(match[2]!), + }; + } + + const chunk = await reader.read(); + if (chunk.done) { + throw new Error('SSE stream closed before the next event was available.'); + } + + buffer += decoder.decode(chunk.value, { stream: true }); + } + }, + + async cancel() { + await reader.cancel(); + }, + }; +} + +describe('http adapter', () => { + test('starts sessions, sends events, reads snapshots, and streams emitted events', async () => { + const machine = createAgentMachine({ + id: 'http-adapter-test', + schemas: { + input: z.object({ + text: z.string(), + }), + events: { + begin: z.object({}), + }, + emitted: { + textPart: z.object({ + delta: z.string(), + }), + }, + }, + context: (input) => ({ + text: input.text, + finalText: '', + }), + initial: 'waiting', + states: { + waiting: { + on: { + begin: { + target: 'writing', + }, + }, + }, + writing: { + schemas: { output: z.object({ + text: z.string(), + }) }, + invoke: async ({ context }, enq) => { + enq.emit({ type: 'textPart', delta: context.text }); + return { text: context.text }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { + finalText: output.text, + }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + text: context.finalText, + }), + }, + }, + }); + const controller = createSessionHttpController(machine); + + const startResponse = await controller.handle( + new Request('https://agent.test/sessions', { + method: 'POST', + body: JSON.stringify({ text: 'hello' }), + }) + ); + const startBody = await startResponse.json() as { + sessionId: string; + snapshot: { value: string; status: string }; + }; + + expect(startBody.snapshot).toEqual( + expect.objectContaining({ + value: 'waiting', + status: 'active', + }) + ); + + const streamResponse = await controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}/stream`) + ); + const reader = createSseReader(streamResponse); + + const sendPromise = controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}/events`, { + method: 'POST', + body: JSON.stringify({ type: 'begin' }), + }) + ); + + await expect(reader.next()).resolves.toEqual({ + event: 'textPart', + data: { + type: 'textPart', + delta: 'hello', + }, + }); + await expect(reader.next()).resolves.toEqual({ + event: 'done', + data: { + text: 'hello', + }, + }); + + const sendResponse = await sendPromise; + expect(sendResponse.status).toBe(200); + + const statusResponse = await controller.handle( + new Request(`https://agent.test/sessions/${startBody.sessionId}`) + ); + const statusBody = await statusResponse.json() as { + snapshot: { value: string; status: string; output: unknown }; + }; + + expect(statusBody.snapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + text: 'hello', + }, + }) + ); + }); +}); diff --git a/src/http/index.ts b/src/http/index.ts new file mode 100644 index 0000000..c1a0ee9 --- /dev/null +++ b/src/http/index.ts @@ -0,0 +1,209 @@ +import { + createMemoryRunStore, +} from '../runtime/memory-store.js'; +import { + restoreSession, + startSession, +} from '../runtime/session.js'; +import type { + AgentMachine, + AgentRun, + RunStore, + TransitionEvent, +} from '../types.js'; + +type AnyMachine = AgentMachine; +type RunFor = + TMachine extends AgentMachine< + any, + infer TContext, + infer TEvents, + infer TStates, + infer TOutput, + infer TEmitted + > + ? AgentRun + : AgentRun; + +type InputFor = + TMachine extends AgentMachine + ? TInput + : unknown; + +type EventsFor = + TMachine extends AgentMachine + ? TEvents + : {}; + +export interface SessionHttpController { + handle(request: Request): Promise; + getRun(sessionId: string): Promise>; + dropActiveSession(sessionId: string): void; +} + +export interface SessionHttpControllerOptions { + store?: RunStore; + parseInput?: (request: Request) => Promise>; + parseEvent?: ( + request: Request + ) => Promise>>; +} + +export function createSessionHttpController( + machine: TMachine, + options: SessionHttpControllerOptions = {} +): SessionHttpController { + const store = options.store ?? createMemoryRunStore(); + const activeRuns = new Map>(); + const parseInput = + options.parseInput ?? ((request) => request.json() as Promise>); + const parseEvent = + options.parseEvent ?? + ((request) => request.json() as Promise>>); + + function trackRun(run: RunFor): RunFor { + activeRuns.set(run.sessionId, run); + run.onDone(() => { + activeRuns.delete(run.sessionId); + }); + run.onError(() => { + activeRuns.delete(run.sessionId); + }); + return run; + } + + async function getRun(sessionId: string): Promise> { + const existing = activeRuns.get(sessionId); + if (existing) { + return existing; + } + + const restored = await restoreSession(machine, { + sessionId, + store, + }) as RunFor; + + return trackRun(restored); + } + + return { + getRun, + + dropActiveSession(sessionId) { + activeRuns.delete(sessionId); + }, + + async handle(request) { + const url = new URL(request.url); + const match = url.pathname.match(/^\/sessions(?:\/([^/]+)(?:\/(events|stream))?)?$/); + const sessionId = match?.[1]; + const childRoute = match?.[2]; + + if (request.method === 'POST' && url.pathname === '/sessions') { + const run = await startSession(machine, { + store, + input: await parseInput(request), + }) as RunFor; + + trackRun(run); + + return Response.json({ + sessionId: run.sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'GET' && sessionId && !childRoute) { + const run = await getRun(sessionId); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'POST' && sessionId && childRoute === 'events') { + const run = await getRun(sessionId); + await run.send(await parseEvent(request)); + + return Response.json({ + sessionId, + snapshot: run.getSnapshot(), + }); + } + + if (request.method === 'GET' && sessionId && childRoute === 'stream') { + return createRunSseResponse(await getRun(sessionId)); + } + + return new Response('Not found', { status: 404 }); + }, + }; +} + +export function createSessionHttpHandler( + machine: TMachine, + options: SessionHttpControllerOptions = {} +): (request: Request) => Promise { + const controller = createSessionHttpController(machine, options); + return (request) => controller.handle(request); +} + +export function createRunSseResponse( + run: AgentRun +): Response { + let cleanup = () => {}; + + const stream = new ReadableStream({ + start(controller) { + const encoder = new TextEncoder(); + const write = (event: string, data: unknown) => { + controller.enqueue( + encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`) + ); + }; + + if (run.getSnapshot().status === 'done') { + write('done', run.getSnapshot().output); + controller.close(); + return; + } + + if (run.getSnapshot().status === 'error') { + write('error', { error: String(run.getSnapshot().error) }); + controller.close(); + return; + } + + const offEmitted = run.onEmitted((event) => { + write(event.type, event); + }); + const offDone = run.onDone((event) => { + write('done', event.output); + cleanup(); + controller.close(); + }); + const offError = run.onError((event) => { + write('error', { error: String(event.error) }); + cleanup(); + controller.close(); + }); + + cleanup = () => { + offEmitted(); + offDone(); + offError(); + }; + }, + cancel() { + cleanup(); + }, + }); + + return new Response(stream, { + headers: { + 'content-type': 'text/event-stream', + 'cache-control': 'no-cache', + }, + }); +} diff --git a/src/index.ts b/src/index.ts index 8e2826a..9ced35d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,55 @@ -export { createAgent } from './agent'; -export { fromText, fromTextStream } from './text'; -export { fromDecision } from './decision'; -export * from './types'; +// Core +export { createAgentMachine } from './machine.js'; +export { decide, decideResultSchema, requireAdapter } from './decide.js'; +export { classify, classifyResultSchema } from './classify.js'; + +// Adapter +export { createAdapter } from './adapter.js'; +export { createMemoryRunStore } from './runtime/memory-store.js'; +export { restoreSession, startSession } from './runtime/session.js'; +export { waitForRunDone, waitForRunSnapshot } from './runtime/index.js'; +export { + appendMessages, + assistantMessage, + systemMessage, + userMessage, +} from './utils.js'; + +// Types +export type { + AgentAdapter, + AgentMachine, + AgentMessage, + AgentRun, + AgentResolverSnapshot, + AgentSnapshot, + AgentState, + AgentToolChoice, + AgentTools, + ClassifyOptions, + ClassifyResultFor, + DecideOptions, + DecideAdapter, + DecideResultFor, + EmittedPart, + EmittedUnion, + EventPayload, + EventUnion, + ExecuteResult, + InferOutput, + InvokeEnqueue, + JournalEvent, + JournalEventRecord, + MachineConfig, + PersistedSnapshot, + ResolvableStateValue, + RestoreSessionOptions, + RunStore, + SessionOptions, + StandardSchemaV1, + StateConfig, + StateResolverArgs, + Trace, + TransitionEvent, + TransitionResult, +} from './types.js'; diff --git a/src/invoke-events.test.ts b/src/invoke-events.test.ts new file mode 100644 index 0000000..5498bd4 --- /dev/null +++ b/src/invoke-events.test.ts @@ -0,0 +1,136 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from './index.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + const off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('invoke success is journaled as an internal machine event', async () => { + const machine = createAgentMachine({ + id: 'invoke-success', + context: () => ({ result: null as string | null }), + initial: 'processing', + states: { + processing: { + schemas: { output: z.object({ value: z.string() }) }, + invoke: async () => ({ value: 'ok' }), + onDone: ({ output }) => ({ + target: 'done', + context: { result: output.value }, + }), + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + await once(run.onDone.bind(run)); + const journal = await store.loadEvents(run.sessionId); + + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + context: { result: 'ok' }, + output: { result: 'ok' }, + }) + ); + expect(journal).toEqual([ + expect.objectContaining({ sequence: 1, type: 'xstate.init' }), + expect.objectContaining({ + sequence: 2, + type: 'xstate.done.invoke.processing', + output: { value: 'ok' }, + }), + ]); +}); + +test('invoke failure is journaled as an internal machine event', async () => { + const machine = createAgentMachine({ + id: 'invoke-failure', + context: () => ({ count: 0 }), + initial: 'processing', + states: { + processing: { + invoke: async () => { + throw new Error('boom'); + }, + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + await once(run.onError.bind(run)); + const journal = await store.loadEvents(run.sessionId); + + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'processing', + status: 'error', + context: { count: 0 }, + error: expect.objectContaining({ message: 'boom' }), + }) + ); + expect(journal).toEqual([ + expect.objectContaining({ sequence: 1, type: 'xstate.init' }), + expect.objectContaining({ + sequence: 2, + type: 'xstate.error.invoke.processing', + error: expect.objectContaining({ message: 'boom' }), + }), + ]); +}); + +test('invalid invoke results fail without journaling a done event', async () => { + const machine = createAgentMachine({ + id: 'invoke-invalid-result', + context: () => ({ count: 0 }), + initial: 'processing', + states: { + processing: { + schemas: { output: z.object({ value: z.string() }) }, + invoke: async () => ({ value: 42 } as unknown as { value: string }), + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + await once(run.onError.bind(run)); + const journal = await store.loadEvents(run.sessionId); + + expect(journal.map((event) => event.type)).toEqual([ + 'xstate.init', + 'xstate.error.invoke.processing', + ]); + expect(journal).not.toContainEqual( + expect.objectContaining({ + type: 'xstate.done.invoke.processing', + }) + ); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + status: 'error', + error: expect.objectContaining({ + message: expect.stringContaining('Validation failed'), + }), + }) + ); +}); diff --git a/src/langgraph-equivalents/branching.test.ts b/src/langgraph-equivalents/branching.test.ts new file mode 100644 index 0000000..784594c --- /dev/null +++ b/src/langgraph-equivalents/branching.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +test('supports branching-style orchestration with plain async fan-out inside invoke', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-branching', + schemas: { + input: z.object({ topic: z.string() }), + }, + context: (input) => ({ + topic: input.topic, + docs: null as string | null, + issues: null as string | null, + code: null as string | null, + summary: null as string | null, + }), + initial: 'analyzing', + states: { + analyzing: { + schemas: { output: z.object({ + docs: z.string(), + issues: z.string(), + code: z.string(), + }) }, + invoke: async ({ context }) => { + const [docs, issues, code] = await Promise.all([ + Promise.resolve(`docs about ${context.topic}`), + Promise.resolve(`issues about ${context.topic}`), + Promise.resolve(`code about ${context.topic}`), + ]); + + return { docs, issues, code }; + }, + onDone: ({ output }) => ({ + target: 'summarizing', + context: output, + }), + }, + summarizing: { + // paramsschema could help here, the summary has lots of string | null + schemas: { output: z.object({ summary: z.string() }) }, + invoke: async ({ context }) => ({ + summary: [context.docs, context.issues, context.code].join(' | '), + }), + onDone: ({ output }) => ({ + target: 'done', + context: { summary: output.summary }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + docs: context.docs, + issues: context.issues, + code: context.code, + summary: context.summary, + }), + }, + }, + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'agents' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + docs: 'docs about agents', + issues: 'issues about agents', + code: 'code about agents', + summary: 'docs about agents | issues about agents | code about agents', + }); + } +}); diff --git a/src/langgraph-equivalents/chatbot-messages.test.ts b/src/langgraph-equivalents/chatbot-messages.test.ts new file mode 100644 index 0000000..1174b1c --- /dev/null +++ b/src/langgraph-equivalents/chatbot-messages.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from 'vitest'; +import { createChatbotMessagesExample } from '../../examples/index.js'; + +test('message-centric chatbot workflow accumulates structured messages across turns', async () => { + const machine = createChatbotMessagesExample(async (messages) => ({ + message: { + role: 'assistant', + content: `Replying to: ${messages.at(-1)?.content ?? ''}`, + }, + })); + + const afterFirstTurn = machine.transition(machine.getInitialState(), { + type: 'messages.user', + message: { + role: 'user', + content: 'Hello there', + }, + }); + const firstResult = await machine.execute(afterFirstTurn); + + expect(firstResult.status).toBe('pending'); + if (firstResult.status === 'pending') { + expect(firstResult.messages).toEqual([ + { role: 'user', content: 'Hello there' }, + { role: 'assistant', content: 'Replying to: Hello there' }, + ]); + + const afterSecondTurn = machine.transition(firstResult.state, { + type: 'messages.user', + message: { + role: 'user', + content: 'Can you expand on that?', + }, + }); + const secondResult = await machine.execute(afterSecondTurn); + + expect(secondResult.status).toBe('pending'); + if (secondResult.status === 'pending') { + expect(secondResult.messages).toEqual([ + { role: 'user', content: 'Hello there' }, + { role: 'assistant', content: 'Replying to: Hello there' }, + { role: 'user', content: 'Can you expand on that?' }, + { role: 'assistant', content: 'Replying to: Can you expand on that?' }, + ]); + } + } +}); diff --git a/src/langgraph-equivalents/conditional-subflow.test.ts b/src/langgraph-equivalents/conditional-subflow.test.ts new file mode 100644 index 0000000..7b0a155 --- /dev/null +++ b/src/langgraph-equivalents/conditional-subflow.test.ts @@ -0,0 +1,51 @@ +import { expect, test } from 'vitest'; +import { createConditionalSubflowExample } from '../../examples/index.js'; + +test('conditionally enters the research subflow from parent input', async () => { + const machine = createConditionalSubflowExample({ + research: async (topic) => ({ + bullets: [`${topic}:fact-1`, `${topic}:fact-2`], + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + topic: 'agent graphs', + mode: 'research', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + mode: 'research', + bullets: ['agent graphs:fact-1', 'agent graphs:fact-2'], + draft: null, + }); + } +}); + +test('conditionally enters the draft subflow with parent-provided input', async () => { + const machine = createConditionalSubflowExample({ + draft: async ({ topic, bullets }) => ({ + draft: `${topic}: ${bullets.join(' / ')}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ + topic: 'agent graphs', + mode: 'draft', + bullets: ['known fact', 'second fact'], + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + mode: 'draft', + bullets: ['known fact', 'second fact'], + draft: 'agent graphs: known fact / second fact', + }); + } +}); diff --git a/src/langgraph-equivalents/error-retry.test.ts b/src/langgraph-equivalents/error-retry.test.ts new file mode 100644 index 0000000..d595a37 --- /dev/null +++ b/src/langgraph-equivalents/error-retry.test.ts @@ -0,0 +1,118 @@ +import { expect, test, vi } from 'vitest'; +import { createErrorRetryExample } from '../../examples/index.js'; +import { createMemoryRunStore, restoreSession } from '../index.js'; + +test('retries failed invoke work through explicit internal error events', async () => { + let attempts = 0; + const machine = createErrorRetryExample(async ({ attempt }) => { + attempts += 1; + + if (attempt < 3) { + throw new Error(`temporary failure ${attempt}`); + } + + return { + answer: `answered on attempt ${attempt}`, + }; + }); + + const result = await machine.execute( + machine.getInitialState({ question: 'What is durable retry?' }) + ); + + expect(attempts).toBe(3); + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + answer: 'answered on attempt 3', + attempts: 3, + errors: ['temporary failure 1', 'temporary failure 2'], + }); + } +}); + +test('fails after the configured retry budget is exhausted', async () => { + const machine = createErrorRetryExample(async ({ attempt }) => { + throw new Error(`still down ${attempt}`); + }, 2); + + const result = await machine.execute( + machine.getInitialState({ question: 'Will this recover?' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + answer: null, + attempts: 2, + errors: ['still down 1', 'still down 2'], + }); + } +}); + +test('restores a durable retry snapshot and continues from the next attempt', async () => { + const sessionId = 'durable-retry-session'; + const machine = createErrorRetryExample(async ({ attempt }) => ({ + answer: `restored attempt ${attempt}`, + })); + const store = createMemoryRunStore(); + const input = { question: 'Can retry survive restore?' }; + const initial = machine.getInitialState(input); + const retryState = machine.transition(initial, { + type: 'xstate.error.invoke.answering', + error: { message: 'network reset' }, + at: 2, + }); + + await store.append(sessionId, { + type: 'xstate.init', + input, + at: 1, + }); + await store.append(sessionId, { + type: 'xstate.error.invoke.answering', + error: { message: 'network reset' }, + at: 2, + }); + await store.saveSnapshot({ + sessionId, + afterSequence: 2, + snapshot: { + value: retryState.value, + context: retryState.context, + messages: retryState.messages, + status: retryState.status, + input: retryState.input, + createdAt: 1, + sessionId, + }, + createdAt: 2, + }); + + const restored = await restoreSession(machine, { + sessionId, + store, + }); + + await vi.waitFor(() => { + expect(restored.getSnapshot().status).toBe('done'); + }); + + expect(restored.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + context: { + question: 'Can retry survive restore?', + answer: 'restored attempt 2', + attempt: 2, + errors: ['network reset'], + }, + output: { + answer: 'restored attempt 2', + attempts: 2, + errors: ['network reset'], + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/graph.test.ts b/src/langgraph-equivalents/graph.test.ts new file mode 100644 index 0000000..d6de3f1 --- /dev/null +++ b/src/langgraph-equivalents/graph.test.ts @@ -0,0 +1,118 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +test('supports multi-step workflow accumulation like a sequential state graph', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-sequence', + context: () => ({ messages: [] as string[] }), + initial: 'node1', + states: { + node1: { + schemas: { output: z.object({ messages: z.array(z.string()) }) }, + invoke: async () => ({ messages: ['from node1'] }), + onDone: ({ output, context }) => ({ + target: 'node2', + context: { messages: [...context.messages, ...output.messages] }, + }), + }, + node2: { + schemas: { output: z.object({ messages: z.array(z.string()) }) }, + invoke: async () => ({ messages: ['from node2'] }), + onDone: ({ output, context }) => ({ + target: 'node3', + context: { messages: [...context.messages, ...output.messages] }, + }), + }, + node3: { + schemas: { output: z.object({ messages: z.array(z.string()) }) }, + invoke: async () => ({ messages: ['from node3'] }), + onDone: ({ output, context }) => ({ + target: 'done', + context: { messages: [...context.messages, ...output.messages] }, + }), + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const result = await machine.execute(machine.getInitialState()); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + messages: ['from node1', 'from node2', 'from node3'], + }); + } +}); + +test('supports conditional routing with explicit machine transitions', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-routing', + schemas: { + input: z.object({ request: z.string() }), + }, + context: (input) => ({ + request: input.request, + route: null as string | null, + handledBy: null as string | null, + }), + initial: 'routeRequest', + states: { + routeRequest: { + schemas: { output: z.object({ + route: z.enum(['billing', 'general']), + }) }, + invoke: async ({ context }) => { + const route = context.request.toLowerCase().includes('refund') + ? 'billing' + : 'general'; + + return { route } as const; + }, + onDone: ({ output }) => ({ + target: output.route, + context: { route: output.route }, + }), + }, + billing: { + schemas: { output: z.object({ handledBy: z.literal('billing') }) }, + invoke: async () => ({ handledBy: 'billing' as const }), // why do we need to cast to const here? + onDone: ({ output }) => ({ + target: 'done', + context: { handledBy: output.handledBy }, + }), + }, + general: { + schemas: { output: z.object({ handledBy: z.literal('general') }) }, + invoke: async () => ({ handledBy: 'general' as const }), + onDone: ({ output }) => ({ + target: 'done', + context: { handledBy: output.handledBy }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + route: context.route, + handledBy: context.handledBy, + }), + }, + }, + }); + + const result = await machine.execute( + machine.getInitialState({ request: 'I need a refund for my invoice.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + route: 'billing', + handledBy: 'billing', + }); + } +}); diff --git a/src/langgraph-equivalents/hitl.test.ts b/src/langgraph-equivalents/hitl.test.ts new file mode 100644 index 0000000..9f82a76 --- /dev/null +++ b/src/langgraph-equivalents/hitl.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +test('supports human-in-the-loop review with explicit pending states and external events', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-hitl', + schemas: { + input: z.object({ task: z.string() }), + events: { + approve: z.object({}), + revise: z.object({ note: z.string() }), + }, + }, + context: (input) => ({ + task: input.task, + notes: [] as string[], + draft: null as string | null, + }), + initial: 'drafting', + states: { + drafting: { + schemas: { output: z.object({ draft: z.string() }) }, + invoke: async ({ context }) => ({ + draft: `Draft for ${context.task}${context.notes.length ? ` (${context.notes.join(', ')})` : ''}`, + }), + onDone: ({ output }) => ({ + target: 'review', + context: { draft: output.draft }, + }), + }, + review: { + on: { + approve: { target: 'done' }, + revise: ({ event, context }) => ({ + target: 'drafting', + context: { notes: [...context.notes, event.note] }, + }), + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ draft: context.draft }), + }, + }, + }); + + const first = await machine.execute( + machine.getInitialState({ task: 'reply to customer' }) + ); + + expect(first.status).toBe('pending'); + if (first.status !== 'pending') return; + + expect(first.value).toBe('review'); + expect(first.context.draft).toContain('reply to customer'); + + const revised = machine.transition(first.state, { + type: 'revise', + note: 'make it shorter', + }); + const second = await machine.execute(revised); + + expect(second.status).toBe('pending'); + if (second.status !== 'pending') return; + + const approved = machine.transition(second.state, { type: 'approve' }); + const done = await machine.execute(approved); + + expect(done.status).toBe('done'); + if (done.status === 'done') { + expect(done.output).toEqual({ + draft: 'Draft for reply to customer (make it shorter)', + }); + } +}); diff --git a/src/langgraph-equivalents/map-reduce.test.ts b/src/langgraph-equivalents/map-reduce.test.ts new file mode 100644 index 0000000..7503c33 --- /dev/null +++ b/src/langgraph-equivalents/map-reduce.test.ts @@ -0,0 +1,79 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +test('supports map-reduce style orchestration with dynamic work items inside invoke', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-map-reduce', + schemas: { + input: z.object({ topic: z.string() }), + }, + context: (input) => ({ + topic: input.topic, + subjects: [] as string[], + jokes: [] as string[], + bestJoke: null as string | null, + }), + initial: 'planning', + states: { + planning: { + schemas: { output: z.object({ subjects: z.array(z.string()) }) }, + invoke: async ({ context }) => ({ + subjects: [`${context.topic} basics`, `${context.topic} advanced`], + }), + onDone: ({ output }) => ({ + target: 'mapping', + context: { subjects: output.subjects }, + }), + }, + mapping: { + schemas: { output: z.object({ jokes: z.array(z.string()) }) }, + invoke: async ({ context }) => { + const jokes = await Promise.all( + context.subjects.map(async (subject) => `joke about ${subject}`) + ); + + return { jokes }; + }, + onDone: ({ output }) => ({ + target: 'reducing', + context: { jokes: output.jokes }, + }), + }, + reducing: { + schemas: { output: z.object({ bestJoke: z.string() }) }, + invoke: async ({ context }) => ({ + bestJoke: context.jokes[0] ?? '', + }), + onDone: ({ output }) => ({ + target: 'done', + context: { bestJoke: output.bestJoke }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + subjects: context.subjects, + jokes: context.jokes, + bestJoke: context.bestJoke, + }), + }, + }, + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'state machines' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + subjects: ['state machines basics', 'state machines advanced'], + jokes: [ + 'joke about state machines basics', + 'joke about state machines advanced', + ], + bestJoke: 'joke about state machines basics', + }); + } +}); diff --git a/src/langgraph-equivalents/multi-agent-network.test.ts b/src/langgraph-equivalents/multi-agent-network.test.ts new file mode 100644 index 0000000..5b1c50c --- /dev/null +++ b/src/langgraph-equivalents/multi-agent-network.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from 'vitest'; +import { createMultiAgentNetworkExample } from '../../examples/multi-agent-network.js'; + +test('multi-agent network coordinates specialist handoffs until a final draft is ready', async () => { + let step = 0; + + const machine = createMultiAgentNetworkExample({ + adapter: { + decide: async () => { + step += 1; + + if (step === 1) { + return { + choice: 'research', + data: { focus: 'collect architecture notes' }, + }; + } + + if (step === 2) { + return { + choice: 'write', + data: { angle: 'turn notes into an executive summary' }, + }; + } + + return { + choice: 'finalize', + data: {}, + }; + }, + }, + research: async ({ topic, focus }) => ({ + notes: [`${topic}:${focus}:1`, `${topic}:${focus}:2`], + }), + write: async ({ topic, notes, angle }) => ({ + draft: `${topic} | ${angle} | ${notes.join(' / ')}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ topic: 'agent runtimes' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + topic: 'agent runtimes', + notes: [ + 'agent runtimes:collect architecture notes:1', + 'agent runtimes:collect architecture notes:2', + ], + draft: + 'agent runtimes | turn notes into an executive summary | agent runtimes:collect architecture notes:1 / agent runtimes:collect architecture notes:2', + handoffs: [ + 'researcher:collect architecture notes', + 'writer:turn notes into an executive summary', + ], + }); + } +}); diff --git a/src/langgraph-equivalents/persistence.test.ts b/src/langgraph-equivalents/persistence.test.ts new file mode 100644 index 0000000..efa31ae --- /dev/null +++ b/src/langgraph-equivalents/persistence.test.ts @@ -0,0 +1,88 @@ +import { expect, test, vi } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + restoreSession, + startSession, +} from '../index.js'; + +test('persists and restores a long-running approval workflow', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-persistence', + context: () => ({ + approved: false, + summary: null as string | null, + }), + initial: 'review', + states: { + review: { + on: { + approve: { + target: 'summarize', + context: { approved: true }, + }, + }, + }, + summarize: { + schemas: { output: z.object({ summary: z.string() }) }, + invoke: async ({ context }) => ({ + summary: context.approved ? 'approved summary' : 'rejected summary', + }), + onDone: ({ output }) => ({ + target: 'done', + context: { summary: output.summary }, + }), + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const baseStore = createMemoryRunStore(); + let snapshotWrites = 0; + const store = { + append: baseStore.append, + loadEvents: baseStore.loadEvents, + loadLatestSnapshot: baseStore.loadLatestSnapshot, + async saveSnapshot(snapshot: Awaited< + ReturnType + > extends infer TSaved + ? Exclude + : never) { + snapshotWrites += 1; + if (snapshotWrites === 1) { + await baseStore.saveSnapshot(snapshot); + } + }, + }; + + const liveRun = await startSession(machine, { store }); + await liveRun.send({ type: 'approve' }); + + const restoredRun = await restoreSession(machine, { + sessionId: liveRun.sessionId, + store, + }); + + await vi.waitFor(() => { + expect(restoredRun.getSnapshot()).toEqual(liveRun.getSnapshot()); + }); + + expect(restoredRun.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + context: { + approved: true, + summary: 'approved summary', + }, + output: { + approved: true, + summary: 'approved summary', + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/persistent-multi-agent-network.test.ts b/src/langgraph-equivalents/persistent-multi-agent-network.test.ts new file mode 100644 index 0000000..863d993 --- /dev/null +++ b/src/langgraph-equivalents/persistent-multi-agent-network.test.ts @@ -0,0 +1,63 @@ +import { expect, test } from 'vitest'; +import { runPersistentMultiAgentNetworkExample } from '../../examples/index.js'; + +test('restores a multi-agent handoff workflow from a persisted mid-handoff snapshot', async () => { + let step = 0; + + const result = await runPersistentMultiAgentNetworkExample( + { topic: 'durable agent handoffs' }, + { + adapter: { + decide: async () => { + step += 1; + + if (step === 1) { + return { + choice: 'research', + data: { focus: 'collect the most durable architecture notes' }, + }; + } + + if (step === 2) { + return { + choice: 'write', + data: { angle: 'summarize the handoff-ready findings' }, + }; + } + + return { + choice: 'finalize', + data: {}, + }; + }, + }, + research: async ({ topic, focus }) => ({ + notes: [`${topic}:${focus}:1`, `${topic}:${focus}:2`], + }), + write: async ({ topic, notes, angle }) => ({ + draft: `${topic} | ${angle} | ${notes.join(' / ')}`, + }), + } + ); + + expect(result.restoredSnapshot).toEqual(result.liveSnapshot); + expect(result.restoredSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + topic: 'durable agent handoffs', + notes: [ + 'durable agent handoffs:collect the most durable architecture notes:1', + 'durable agent handoffs:collect the most durable architecture notes:2', + ], + draft: + 'durable agent handoffs | summarize the handoff-ready findings | durable agent handoffs:collect the most durable architecture notes:1 / durable agent handoffs:collect the most durable architecture notes:2', + handoffs: [ + 'researcher:collect the most durable architecture notes', + 'writer:summarize the handoff-ready findings', + ], + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/persistent-streaming.test.ts b/src/langgraph-equivalents/persistent-streaming.test.ts new file mode 100644 index 0000000..386e92e --- /dev/null +++ b/src/langgraph-equivalents/persistent-streaming.test.ts @@ -0,0 +1,26 @@ +import { expect, test } from 'vitest'; +import { runPersistentStreamingExample } from '../../examples/index.js'; + +test('restores a streaming workflow without replaying stale emitted parts', async () => { + const result = await runPersistentStreamingExample(); + + expect(result.initialParts).toEqual(['hel']); + expect(result.restoredParts).toEqual(['lo']); + expect(result.initialSnapshot).toEqual( + expect.objectContaining({ + value: 'writing', + status: 'active', + }) + ); + expect(result.restoredSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { text: 'hello' }, + }) + ); + expect(result.journal.map((event) => event.type)).toEqual([ + 'xstate.init', + 'xstate.done.invoke.writing', + ]); +}); diff --git a/src/langgraph-equivalents/persistent-supervisor.test.ts b/src/langgraph-equivalents/persistent-supervisor.test.ts new file mode 100644 index 0000000..4885808 --- /dev/null +++ b/src/langgraph-equivalents/persistent-supervisor.test.ts @@ -0,0 +1,63 @@ +import { expect, test } from 'vitest'; +import { runPersistentSupervisorExample } from '../../examples/index.js'; + +test('restores a supervisor handoff workflow from a persisted retry snapshot', async () => { + let decisions = 0; + + const result = await runPersistentSupervisorExample( + { request: 'Reverse the duplicate subscription charge.' }, + { + adapter: { + decide: async () => { + decisions += 1; + + if (decisions === 1) { + return { + choice: 'retry', + data: { + instruction: 'Retry using the verified billing email on file.', + }, + }; + } + + return { + choice: 'escalate', + data: { + reason: 'Escalate to billing because the account is still ambiguous.', + }, + }; + }, + }, + handle: async ({ attempt, instruction }) => ({ + status: 'blocked', + issue: + attempt === 1 + ? 'Missing account identifier.' + : `Still blocked after retry: ${instruction}`, + }), + maxAttempts: 2, + } + ); + + expect(result.restoredSnapshot).toEqual(result.liveSnapshot); + expect(result.restoredSnapshot).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + request: 'Reverse the duplicate subscription charge.', + status: 'escalated', + resolution: null, + escalationReason: + 'Escalate to billing because the account is still ambiguous.', + attemptCount: 2, + history: [ + 'worker:1:blocked:Missing account identifier.', + 'supervisor:retry:Retry using the verified billing email on file.', + 'worker:2:blocked:Still blocked after retry: Retry using the verified billing email on file.', + 'supervisor:escalate:Escalate to billing because the account is still ambiguous.', + ], + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/plan-and-execute.test.ts b/src/langgraph-equivalents/plan-and-execute.test.ts new file mode 100644 index 0000000..781b646 --- /dev/null +++ b/src/langgraph-equivalents/plan-and-execute.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from 'vitest'; +import { createPlanAndExecuteExample } from '../../examples/plan-and-execute.js'; + +test('plan-and-execute workflow decomposes a goal and synthesizes a final answer', async () => { + const machine = createPlanAndExecuteExample({ + plan: async () => ({ + plan: ['inspect docs', 'inspect code', 'summarize findings'], + }), + executeStep: async ({ step }) => ({ + result: `done:${step}`, + }), + synthesize: async ({ stepResults }) => ({ + answer: stepResults.join(' | '), + }), + }); + + const result = await machine.execute( + machine.getInitialState({ goal: 'understand the repo' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + goal: 'understand the repo', + plan: ['inspect docs', 'inspect code', 'summarize findings'], + stepResults: [ + 'done:inspect docs', + 'done:inspect code', + 'done:summarize findings', + ], + answer: + 'done:inspect docs | done:inspect code | done:summarize findings', + }); + } +}); diff --git a/src/langgraph-equivalents/prebuilt-react.test.ts b/src/langgraph-equivalents/prebuilt-react.test.ts new file mode 100644 index 0000000..de52d4a --- /dev/null +++ b/src/langgraph-equivalents/prebuilt-react.test.ts @@ -0,0 +1,88 @@ +import { expect, test } from 'vitest'; +import { + createMemoryRunStore, + startSession, +} from '../index.js'; +import { createReactAgentFromScratch } from '../../examples/react-agent-from-scratch.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('react agent from scratch loops through a tool call and returns a final answer', async () => { + const agent = createReactAgentFromScratch({ + prompt: 'You are helpful.', + tools: [ + { + name: 'search', + description: 'Searches for a query', + execute: async (input) => `result for ${String(input.query)}`, + }, + ], + model: async ({ messages }) => { + const last = messages.at(-1); + + if (!last || last.role === 'user') { + return { + kind: 'tool' as const, + toolName: 'search', + input: { query: 'weather in sf' }, + message: 'I should search first.', + }; + } + + if (last.role === 'tool') { + return { + kind: 'final' as const, + message: `Answer based on: ${last.content}`, + }; + } + + throw new Error('Unexpected transcript state'); + }, + }); + + const run = await startSession(agent, { + store: createMemoryRunStore(), + input: { + messages: [{ role: 'user', content: 'What is the weather?' }], + }, + }); + const toolEvents: string[] = []; + + run.on('toolCall', (event) => { + toolEvents.push(`call:${event.toolName}`); + }); + run.on('toolResult', (event) => { + toolEvents.push(`result:${event.toolName}`); + }); + + await once(run.onDone.bind(run)); + + expect(toolEvents).toEqual(['call:search', 'result:search']); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + finalMessage: 'Answer based on: result for weather in sf', + messages: [ + { role: 'system', content: 'You are helpful.' }, + { role: 'user', content: 'What is the weather?' }, + { role: 'assistant', content: 'I should search first.' }, + { role: 'tool', name: 'search', content: 'result for weather in sf' }, + { role: 'assistant', content: 'Answer based on: result for weather in sf' }, + ], + steps: 2, + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/rag.test.ts b/src/langgraph-equivalents/rag.test.ts new file mode 100644 index 0000000..8cc5f8a --- /dev/null +++ b/src/langgraph-equivalents/rag.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from 'vitest'; +import { createRagExample } from '../../examples/index.js'; + +test('rag workflow retrieves documents and synthesizes a grounded answer', async () => { + const machine = createRagExample({ + retrieve: async (question) => ({ + documents: [ + { id: 'doc-1', content: `${question} :: first fact` }, + { id: 'doc-2', content: `${question} :: second fact` }, + ], + }), + adapter: { + generateText: async ({ prompt }) => ({ + answer: String(prompt) + .replace('Question: ', '') + .replace('\n\nDocuments:\n- [doc-1] ', ' => ') + .replace('\n- [doc-2] ', ' | '), + }), + }, + }); + + const result = await machine.execute( + machine.getInitialState({ question: 'What is LangGraph?' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + question: 'What is LangGraph?', + documents: [ + { id: 'doc-1', content: 'What is LangGraph? :: first fact' }, + { id: 'doc-2', content: 'What is LangGraph? :: second fact' }, + ], + answer: + 'What is LangGraph? => What is LangGraph? :: first fact | What is LangGraph? :: second fact', + }); + } +}); diff --git a/src/langgraph-equivalents/reflection.test.ts b/src/langgraph-equivalents/reflection.test.ts new file mode 100644 index 0000000..f248a2f --- /dev/null +++ b/src/langgraph-equivalents/reflection.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest'; +import { createReflectionExample } from '../../examples/reflection.js'; + +test('reflection workflow revises a draft until critique is cleared', async () => { + const machine = createReflectionExample({ + draft: async () => ({ + draft: 'Initial draft', + }), + reflect: async ({ revisionCount }) => ({ + feedback: revisionCount === 0 ? 'Add more detail.' : null, + }), + revise: async ({ draft, feedback }) => ({ + draft: `${draft} Revised: ${feedback}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ task: 'Write a short explanation.' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + task: 'Write a short explanation.', + draft: 'Initial draft Revised: Add more detail.', + feedback: null, + revisionCount: 1, + }); + } +}); diff --git a/src/langgraph-equivalents/rewoo.test.ts b/src/langgraph-equivalents/rewoo.test.ts new file mode 100644 index 0000000..3ee9509 --- /dev/null +++ b/src/langgraph-equivalents/rewoo.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from 'vitest'; +import { createRewooExample } from '../../examples/rewoo.js'; + +test('rewoo workflow plans named steps, resolves references, and synthesizes a final answer', async () => { + const machine = createRewooExample({ + plan: async () => ({ + steps: [ + { + id: 'E1', + instruction: 'Find the framework', + input: 'LangGraphJS runtime', + }, + { + id: 'E2', + instruction: 'Summarize the finding', + input: 'Use #E1 to produce a concise takeaway', + }, + ], + }), + executeStep: async ({ step, resolvedInput }) => ({ + result: `${step.id}:${resolvedInput}`, + }), + solve: async ({ resultsById }) => ({ + answer: `${resultsById.E1} | ${resultsById.E2}`, + }), + }); + + const result = await machine.execute( + machine.getInitialState({ objective: 'understand the runtime' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + objective: 'understand the runtime', + steps: [ + { + id: 'E1', + instruction: 'Find the framework', + input: 'LangGraphJS runtime', + }, + { + id: 'E2', + instruction: 'Summarize the finding', + input: 'Use #E1 to produce a concise takeaway', + }, + ], + resultsById: { + E1: 'E1:LangGraphJS runtime', + E2: 'E2:Use E1:LangGraphJS runtime to produce a concise takeaway', + }, + answer: + 'E1:LangGraphJS runtime | E2:Use E1:LangGraphJS runtime to produce a concise takeaway', + }); + } +}); diff --git a/src/langgraph-equivalents/sql-agent.test.ts b/src/langgraph-equivalents/sql-agent.test.ts new file mode 100644 index 0000000..9100120 --- /dev/null +++ b/src/langgraph-equivalents/sql-agent.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from 'vitest'; +import { createMemoryRunStore, startSession } from '../index.js'; +import { createSqlAgentExample } from '../../examples/sql-agent.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('sql-agent workflow retries after a bad query and answers once rows are available', async () => { + let decisions = 0; + + const machine = createSqlAgentExample({ + adapter: { + decide: async () => { + decisions += 1; + + if (decisions === 1) { + return { + choice: 'query', + data: { + query: 'SELECT total FROM invoices WHERE customer = "Acme"', + }, + }; + } + + if (decisions === 2) { + return { + choice: 'query', + data: { + query: 'SELECT customer, total FROM invoices WHERE customer = \'Acme\'', + }, + }; + } + + return { + choice: 'answer', + data: { + answer: 'Acme has one invoice total of 42.', + }, + }; + }, + }, + executeQuery: async ({ query }) => { + if (query.includes('"Acme"')) { + return { + status: 'error' as const, + error: 'SQL syntax error near double quotes.', + }; + } + + return { + status: 'success' as const, + rows: [{ customer: 'Acme', total: 42 }], + }; + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { + question: 'What is Acme owed?', + schema: 'invoices(customer text, total integer)', + }, + }); + const events: string[] = []; + + run.on('toolCall', (event) => { + events.push(`call:${event.input.query}`); + }); + run.on('toolResult', (event) => { + events.push(`result:${event.output.status}`); + }); + + await once(run.onDone.bind(run)); + + expect(events).toEqual([ + 'call:SELECT total FROM invoices WHERE customer = "Acme"', + 'result:error', + "call:SELECT customer, total FROM invoices WHERE customer = 'Acme'", + 'result:success', + ]); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { + question: 'What is Acme owed?', + schema: 'invoices(customer text, total integer)', + answer: 'Acme has one invoice total of 42.', + latestRows: [{ customer: 'Acme', total: 42 }], + latestError: null, + queryHistory: [ + 'SELECT total FROM invoices WHERE customer = "Acme"', + "SELECT customer, total FROM invoices WHERE customer = 'Acme'", + ], + }, + }) + ); +}); diff --git a/src/langgraph-equivalents/streaming.test.ts b/src/langgraph-equivalents/streaming.test.ts new file mode 100644 index 0000000..e35d997 --- /dev/null +++ b/src/langgraph-equivalents/streaming.test.ts @@ -0,0 +1,70 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../index.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('streams live invoke output while preserving durable state history', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-streaming', + schemas: { + emitted: { + textPart: z.object({ delta: z.string() }), + }, + }, + context: () => ({ text: '' }), + initial: 'write', + states: { + write: { + schemas: { output: z.object({ text: z.string() }) }, + invoke: async (_args, enq) => { + enq.emit({ type: 'textPart', delta: 'hello' }); + enq.emit({ type: 'textPart', delta: ' world' }); + return { text: 'hello world' }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { text: output.text }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ text: context.text }), + }, + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + const liveParts: string[] = []; + + run.on('textPart', (part) => { + liveParts.push(part.delta); + }); + + await once(run.onDone.bind(run)); + + expect(liveParts).toEqual(['hello', ' world']); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { text: 'hello world' }, + }) + ); +}); diff --git a/src/langgraph-equivalents/subflow.test.ts b/src/langgraph-equivalents/subflow.test.ts new file mode 100644 index 0000000..15b0370 --- /dev/null +++ b/src/langgraph-equivalents/subflow.test.ts @@ -0,0 +1,101 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; + +test('supports subflow composition by executing a child machine inside a parent invoke', async () => { + const childMachine = createAgentMachine({ + id: 'child-research', + schemas: { + input: z.object({ topic: z.string() }), + }, + context: (input) => ({ + topic: input.topic, + bullets: [] as string[], + }), + initial: 'researching', + states: { + researching: { + schemas: { output: z.object({ bullets: z.array(z.string()) }) }, + invoke: async ({ context }) => ({ + bullets: [`fact about ${context.topic}`, `another fact about ${context.topic}`], + }), + onDone: ({ output }) => ({ + target: 'done', + context: { bullets: output.bullets }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ bullets: context.bullets }), + }, + }, + }); + + const parentMachine = createAgentMachine({ + id: 'parent-writer', + schemas: { + input: z.object({ topic: z.string() }), + }, + context: (input) => ({ + topic: input.topic, + bullets: [] as string[], + draft: null as string | null, + }), + initial: 'researching', + states: { + researching: { + schemas: { output: z.object({ bullets: z.array(z.string()) }) }, + invoke: async ({ context }) => { + const result = await childMachine.execute( + childMachine.getInitialState({ topic: context.topic }) + ); + + if (result.status !== 'done') { + throw new Error('Child machine did not finish'); + } + + return { + bullets: (result.output as { bullets: string[] }).bullets, + }; + }, + onDone: ({ output }) => ({ + target: 'writing', + context: { bullets: output.bullets }, + }), + }, + writing: { + schemas: { output: z.object({ draft: z.string() }) }, + invoke: async ({ context }) => ({ + draft: `${context.topic}: ${context.bullets.join('; ')}`, + }), + onDone: ({ output }) => ({ + target: 'done', + context: { draft: output.draft }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ + bullets: context.bullets, + draft: context.draft, + }), + }, + }, + }); + + const result = await parentMachine.execute( + parentMachine.getInitialState({ topic: 'state machines' }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + bullets: [ + 'fact about state machines', + 'another fact about state machines', + ], + draft: + 'state machines: fact about state machines; another fact about state machines', + }); + } +}); diff --git a/src/langgraph-equivalents/supervisor.test.ts b/src/langgraph-equivalents/supervisor.test.ts new file mode 100644 index 0000000..e2d71f9 --- /dev/null +++ b/src/langgraph-equivalents/supervisor.test.ts @@ -0,0 +1,62 @@ +import { expect, test } from 'vitest'; +import { createSupervisorExample } from '../../examples/supervisor.js'; + +test('supervisor workflow retries a blocked worker and escalates when repeated attempts fail', async () => { + let decisions = 0; + + const machine = createSupervisorExample({ + adapter: { + decide: async () => { + decisions += 1; + + if (decisions === 1) { + return { + choice: 'retry', + data: { + instruction: 'Retry using the customer email already on file.', + }, + }; + } + + return { + choice: 'escalate', + data: { + reason: 'Escalate to billing because the request still lacks a verified account match.', + }, + }; + }, + }, + handle: async ({ attempt, instruction }) => ({ + status: 'blocked', + issue: + attempt === 1 + ? 'Missing account identifier.' + : `Still blocked after retry: ${instruction}`, + }), + maxAttempts: 2, + }); + + const result = await machine.execute( + machine.getInitialState({ + request: 'Refund the duplicate annual subscription charge.', + }) + ); + + expect(result.status).toBe('done'); + if (result.status === 'done') { + expect(result.output).toEqual({ + request: 'Refund the duplicate annual subscription charge.', + status: 'escalated', + resolution: null, + escalationReason: + 'Escalate to billing because the request still lacks a verified account match.', + attemptCount: 2, + history: [ + 'worker:1:blocked:Missing account identifier.', + 'supervisor:retry:Retry using the customer email already on file.', + 'worker:2:blocked:Still blocked after retry: Retry using the customer email already on file.', + 'supervisor:escalate:Escalate to billing because the request still lacks a verified account match.', + ], + }); + } +}); diff --git a/src/langgraph-equivalents/tool-calling.test.ts b/src/langgraph-equivalents/tool-calling.test.ts new file mode 100644 index 0000000..1b781f0 --- /dev/null +++ b/src/langgraph-equivalents/tool-calling.test.ts @@ -0,0 +1,123 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../index.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('supports tool-call style invokes with live tool events and final output', async () => { + const machine = createAgentMachine({ + id: 'langgraph-equivalent-tool-calling', + schemas: { + emitted: { + toolCall: z.object({ + toolName: z.string(), + input: z.object({ city: z.string() }), + }), + toolProgress: z.object({ + toolName: z.string(), + message: z.string(), + step: z.number().int().min(1), + }), + toolResult: z.object({ + toolName: z.string(), + output: z.object({ forecast: z.string() }), + }), + }, + input: z.object({ city: z.string() }), + }, + context: (input) => ({ + city: input.city, + forecast: null as string | null, + }), + initial: 'checkingWeather', + states: { + checkingWeather: { + schemas: { output: z.object({ forecast: z.string() }) }, + invoke: async ({ context }, enq) => { + enq.emit({ + type: 'toolCall', + toolName: 'getWeather', + input: { city: context.city }, + }); + + enq.emit({ + type: 'toolProgress', + toolName: 'getWeather', + message: `Fetching weather for ${context.city}`, + step: 1, + }); + + enq.emit({ + type: 'toolProgress', + toolName: 'getWeather', + message: `Formatting response for ${context.city}`, + step: 2, + }); + + const output = { forecast: `Sunny in ${context.city}` }; + enq.emit({ + type: 'toolResult', + toolName: 'getWeather', + output, + }); + + return output; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { forecast: output.forecast }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ forecast: context.forecast }), + }, + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + input: { city: 'Boston' }, + }); + const events: string[] = []; + + run.on('toolCall', (event) => { + events.push(`call:${event.toolName}`); + }); + run.on('toolProgress', (event) => { + events.push(`progress:${event.toolName}:${event.step}`); + }); + run.on('toolResult', (event) => { + events.push(`result:${event.toolName}`); + }); + + await once(run.onDone.bind(run)); + + expect(events).toEqual([ + 'call:getWeather', + 'progress:getWeather:1', + 'progress:getWeather:2', + 'result:getWeather', + ]); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + output: { forecast: 'Sunny in Boston' }, + }) + ); +}); diff --git a/src/machine.ts b/src/machine.ts new file mode 100644 index 0000000..8e84b8f --- /dev/null +++ b/src/machine.ts @@ -0,0 +1,877 @@ +import type { + AgentMachine, + AgentMessage, + AgentResolverSnapshot, + AgentToolChoice, + AgentTools, + AgentSnapshot, + AgentState, + EmittedPart, + EventPayload, + ExecuteResult, + InferOutput, + MachineConfig, + StandardSchemaV1, + TransitionResult, +} from './types.js'; +import type { JournalEvent } from './runtime/events.js'; +import { + applyTransition, + findEmittedSchema, + findEventSchema, + formatSchemaIssues, + getAvailableEvents, + getInput, + isAlwaysEventType, + isDoneInvokeEventType, + isErrorInvokeEventType, + isReservedInternalEventType, + resolveInitial, + resolveStateConfig, + serializeError, + validateSchemaSync, +} from './utils.js'; +import type { StateConfigAny } from './utils.js'; + +// ─── Type helpers ─── +/** Output type for onDone: typed from invoke return or state schemas.output when present */ +type OnDoneOutput = NoInfer; + +type EventFor = E extends keyof TEvents & string + ? { type: E } & EventPayload> + : { type: E & string; [k: string]: unknown }; + +type StateResolverArgs< + TContext extends Record, + TInput, +> = { + snapshot: AgentResolverSnapshot; + context: TContext; + messages: AgentMessage[]; + input: NoInfer; +}; + +type ResolvableStateValue< + TValue, + TContext extends Record, + TInput, +> = + | TValue + | ((args: StateResolverArgs) => TValue); + +type StateNodeDef< + TState, + TContext extends Record, + TInput, + TResult, + TEvents, + TTarget extends string, + TInputMap extends Record, + TOutput, +> = { + type?: 'final'; + schemas?: { + input?: StandardSchemaV1; + output?: StandardSchemaV1; + }; + invoke?: (args: { + context: TContext; + messages: AgentMessage[]; + input: NoInfer; + signal?: AbortSignal; + }, enq: { emit(part: EmittedPart): void }) => Promise; + onDone?: (args: { output: OnDoneOutput; context: TContext; messages: AgentMessage[] }) => TransitionResult; + always?: TransitionResult | ((args: { + context: TContext; + messages: AgentMessage[]; + input: NoInfer; + }, enq: { emit(part: EmittedPart): void }) => TransitionResult); + on?: { [E in keyof TEvents & string]?: TransitionResult | ((args: { + event: EventFor; + context: TContext; + messages: AgentMessage[]; + }, enq: { emit(part: EmittedPart): void }) => TransitionResult) }; + events?: Record; + output?: (args: { context: TContext; messages: AgentMessage[] }) => NoInfer; + model?: ResolvableStateValue; + adapter?: import('./types.js').AgentAdapter; + prompt?: ResolvableStateValue; + system?: ResolvableStateValue; + tools?: ResolvableStateValue; + toolChoice?: ResolvableStateValue; +}; + +type StatesMap< + TContext extends Record, + TInputMap extends Record, + TResultMap extends Record, + TOutput, + TEvents, +> = { + [K in keyof TInputMap & keyof TResultMap]: StateNodeDef< + unknown, + TContext, + TInputMap[K], + TResultMap[K], + TEvents, + keyof TInputMap & keyof TResultMap & string, + TInputMap, + TOutput + >; +}; + +// ─── Overload A: schemas.context present ─── +export function createAgentMachine< + TInput, + TContext extends Record, + const TEvents extends Record, + const TInputMap extends Record, + TResultMap extends Record, + const TEmitted extends Record, + TOutput = unknown, +>(config: { + id: string; + schemas: { + context: StandardSchemaV1; + input?: StandardSchemaV1; + events?: TEvents; + emitted?: TEmitted; + output?: StandardSchemaV1; + }; + context: (input: NoInfer) => NoInfer; + messages?: AgentMessage[] | ((input: NoInfer) => AgentMessage[]); + adapter?: import('./types.js').AgentAdapter; + externalEvents?: readonly (keyof TEvents & string)[]; + initial: + | (keyof TInputMap & keyof TResultMap & string) + | ((args: { context: NoInfer }) => { + target: keyof TInputMap & keyof TResultMap & string; + input?: Record; + }); + states: StatesMap, TInputMap, TResultMap, TOutput, TEvents>; +}): AgentMachine, TOutput, TEmitted>; + +// ─── Overload B: no schemas.context ─── +export function createAgentMachine< + TInput, + TContext extends Record, + const TEvents extends Record, + const TInputMap extends Record, + TResultMap extends Record, + const TEmitted extends Record, + TOutput = unknown, +>(config: { + id: string; + schemas: { + input: StandardSchemaV1; + context?: never; + events?: TEvents; + emitted?: TEmitted; + output?: StandardSchemaV1; + }; + context: (input: NoInfer) => TContext; + messages?: AgentMessage[] | ((input: NoInfer) => AgentMessage[]); + adapter?: import('./types.js').AgentAdapter; + externalEvents?: readonly (keyof TEvents & string)[]; + initial: + | (keyof TInputMap & keyof TResultMap & string) + | ((args: { context: TContext }) => { + target: keyof TInputMap & keyof TResultMap & string; + input?: Record; + }); + states: StatesMap; +}): AgentMachine, TOutput, TEmitted>; + +// ─── Overload C: no schemas.input or schemas.context ─── +export function createAgentMachine< + TContext extends Record, + const TEvents extends Record, + const TInputMap extends Record, + TResultMap extends Record, + const TEmitted extends Record, + TOutput = unknown, +>(config: { + id: string; + schemas?: { + input?: never; + context?: never; + events?: TEvents; + emitted?: TEmitted; + output?: StandardSchemaV1; + }; + context: (...args: any[]) => TContext; + messages?: AgentMessage[] | ((input: unknown) => AgentMessage[]); + adapter?: import('./types.js').AgentAdapter; + externalEvents?: readonly (keyof TEvents & string)[]; + initial: + | (keyof TInputMap & keyof TResultMap & string) + | ((args: { context: TContext }) => { + target: keyof TInputMap & keyof TResultMap & string; + input?: Record; + }); + states: StatesMap; +}): AgentMachine, TOutput, TEmitted>; + +// ─── Implementation ─── + +export function createAgentMachine( + machineConfig: MachineConfig +): AgentMachine { + const cfg = machineConfig as MachineConfig; + assertValidConfig(cfg); + + type SnapshotRuntime = { sessionId: string; createdAt: number }; + const EVENT_TOOL_PREFIX = 'event.'; + + function assertValidConfig(config: MachineConfig) { + for (const [stateValue, stateConfig] of Object.entries(config.states)) { + if (!stateConfig.invoke) { + continue; + } + + const hasGenerationFields = + stateConfig.prompt !== undefined + || stateConfig.system !== undefined + || stateConfig.tools !== undefined + || stateConfig.toolChoice !== undefined; + + if (hasGenerationFields) { + throw new Error( + `State '${stateValue}' cannot combine invoke with prompt, system, tools, or toolChoice` + ); + } + } + } + + function createSnapshotRuntime(state: AgentState) { + if (state.sessionId && state.createdAt !== undefined) { + return { + sessionId: state.sessionId, + createdAt: state.createdAt, + }; + } + + const sessionId = + typeof globalThis.crypto !== 'undefined' && + typeof globalThis.crypto.randomUUID === 'function' + ? globalThis.crypto.randomUUID() + : `session-${Math.random().toString(36).slice(2)}`; + + return { + sessionId, + createdAt: Date.now(), + }; + } + + function withRuntimeMetadata( + state: AgentState, + runtime: SnapshotRuntime + ): AgentState { + return resolveStateFields({ + ...state, + sessionId: runtime.sessionId, + createdAt: runtime.createdAt, + }); + } + + function withoutResolvedFields(state: AgentState): AgentState { + const { + model: _model, + prompt: _prompt, + system: _system, + tools: _tools, + toolChoice: _toolChoice, + ...rest + } = state; + + return rest; + } + + function resolveStateFields(state: AgentState): AgentState { + const base = withoutResolvedFields(state); + const sc = resolveStateConfig(cfg, base.value); + const input = getInput(base.value, base.input); + const args = { + snapshot: base, + context: base.context, + messages: base.messages, + input, + }; + + const model = + typeof sc.model === 'function' + ? sc.model(args) + : sc.model; + const prompt = + typeof sc.prompt === 'function' + ? sc.prompt(args) + : sc.prompt; + const system = + typeof sc.system === 'function' + ? sc.system(args) + : sc.system; + const tools = + typeof sc.tools === 'function' + ? sc.tools(args) + : sc.tools; + const toolChoice = + typeof sc.toolChoice === 'function' + ? sc.toolChoice(args) + : sc.toolChoice; + const eventTools = getEventTools(base.value); + const resolvedTools = { + ...(tools ?? {}), + ...eventTools, + }; + + return { + ...base, + ...(model !== undefined ? { model } : {}), + ...(prompt !== undefined ? { prompt } : {}), + ...(system !== undefined ? { system } : {}), + ...(Object.keys(resolvedTools).length > 0 ? { tools: resolvedTools } : {}), + ...(toolChoice !== undefined ? { toolChoice } : {}), + }; + } + + function getEventTools(value: string): AgentTools { + const sc = resolveStateConfig(cfg, value); + if (!sc.on || !isGenerativeState(sc)) { + return {}; + } + + const tools: AgentTools = {}; + const externalEvents = new Set(cfg.externalEvents ?? []); + + for (const eventType of Object.keys(sc.on)) { + if (isReservedInternalEventType(eventType) || externalEvents.has(eventType)) { + continue; + } + + const schema = findEventSchema(cfg, value, eventType); + + tools[`${EVENT_TOOL_PREFIX}${eventType}`] = { + description: `Transition with event '${eventType}'.`, + ...(schema ? { schemas: { input: schema },} : {}), + execute: async (input: unknown = {}) => ({ + ...(input && typeof input === 'object' ? input : {}), + type: eventType, + }), + }; + } + + return tools; + } + + function isGenerativeState(sc: StateConfigAny): boolean { + return ( + sc.prompt !== undefined + || sc.system !== undefined + || sc.tools !== undefined + || sc.toolChoice !== undefined + ); + } + + function getInitialState(...args: [input?: unknown]): AgentState { + const input = args[0]; + + let validatedInput = input; + if (cfg.schemas?.input) { + validatedInput = validateSchemaSync(cfg.schemas.input, input); + } + + const context = cfg.context(validatedInput); + const messages = + typeof cfg.messages === 'function' + ? cfg.messages(validatedInput) + : cfg.messages ?? []; + const init = resolveInitial(cfg.initial, { context, input: {} }); + + if (!init.target) { + throw new Error('Initial transition must specify a target state'); + } + + return resolveStateFields({ + value: init.target, + context: init.context ? { ...context, ...init.context } : context, + messages: init.messages ?? messages, + status: 'active', + input: init.input ? { [init.target]: init.input } : {}, + }); + } + + function resolveState(raw: { + value: string; + context: Record; + messages?: AgentMessage[]; + input?: Record>; + sessionId?: string; + createdAt?: number; + status?: AgentState['status']; + output?: unknown; + error?: unknown; + }): AgentState { + return resolveStateFields({ + value: raw.value, + context: raw.context, + messages: raw.messages ?? [], + status: raw.status ?? 'active', + input: raw.input ?? {}, + sessionId: raw.sessionId, + createdAt: raw.createdAt, + output: raw.output, + error: raw.error, + }); + } + + function transition( + state: AgentState, + event: { type: string; [k: string]: unknown } + ): AgentState { + return transitionWithEffects(state, event).next; + } + + function transitionWithEffects( + state: AgentState, + event: { type: string; [k: string]: unknown }, + onEmit?: (part: EmittedPart) => void + ): { next: AgentState; emitted: EmittedPart[] } { + const emitted: EmittedPart[] = []; + const enqueue = createEnqueue((part) => { + emitted.push(part); + onEmit?.(part); + }); + const sc = resolveStateConfig(cfg, state.value); + function applyResult( + result: TransitionResult, + status = state.status + ): AgentState { + if (result.target) { + return resolveStateFields(applyTransition(withoutResolvedFields(state), result)); + } + + return resolveStateFields({ + ...withoutResolvedFields(state), + status, + context: result.context + ? { ...state.context, ...result.context } + : state.context, + messages: result.messages ?? state.messages, + }); + } + + function resolveHandlerResult( + handler: + | TransitionResult + | ((args: { + event: { type: string; [k: string]: unknown }; + context: Record; + messages: AgentMessage[]; + input: Record; + }, enq: { emit(part: EmittedPart): void }) => TransitionResult), + status = state.status + ): { next: AgentState; emitted: EmittedPart[] } { + const result: TransitionResult = + typeof handler === 'function' + ? handler({ + context: state.context, + messages: state.messages, + input: getInput(state.value, state.input), + event, + }, enqueue) + : handler; + + return { + next: applyResult(result, status), + emitted, + }; + } + + if (isDoneInvokeEventType(state.value, event.type)) { + const result = 'output' in event ? event.output : undefined; + const validatedOutput = sc.schemas?.output + ? validateSchemaSync(sc.schemas.output, result) + : result; + + if (sc.onDone) { + const trans = sc.onDone({ + output: validatedOutput, + context: state.context, + messages: state.messages, + }); + + return { + next: applyResult(trans, 'pending'), + emitted, + }; + } + + const internalHandler = sc.on?.[event.type]; + if (internalHandler !== undefined) { + return resolveHandlerResult(internalHandler, 'pending'); + } + + return { next: resolveStateFields({ ...withoutResolvedFields(state), status: 'pending' }), emitted }; + } + + if (isAlwaysEventType(state.value, event.type)) { + if (!sc.always) { + throw new Error(`No always transition in state '${state.value}'`); + } + + return resolveHandlerResult( + sc.always, + state.status + ); + } + + if (isErrorInvokeEventType(state.value, event.type)) { + const internalHandler = sc.on?.[event.type]; + if (internalHandler !== undefined) { + return resolveHandlerResult(internalHandler); + } + + return { + next: resolveStateFields({ + ...withoutResolvedFields(state), + status: 'error', + error: 'error' in event ? event.error : undefined, + }), + emitted, + }; + } + + validateEventPayload(state.value, event); + + if (sc.on?.[event.type] !== undefined) { + const handler = sc.on[event.type]!; + return resolveHandlerResult(handler); + } + + throw new Error( + `No handler for event '${event.type}' in state '${state.value}'` + ); + } + + function validateReplayableResult( + value: string, + result: unknown + ): unknown { + const sc = resolveStateConfig(cfg, value); + if (!sc.schemas?.output) { + return result; + } + + return validateSchemaSync(sc.schemas.output, result); + } + + function validateEventPayload( + value: string, + event: { type: string } + ): void { + const schema = findEventSchema(cfg, value, event.type); + if (!schema) return; + const result = schema['~standard'].validate(event); + if (result instanceof Promise) return; + if ( + result && + typeof result === 'object' && + 'issues' in result && + result.issues + ) { + const messages = formatSchemaIssues( + result.issues as Array<{ message: string }> + ); + throw new Error(`Invalid event '${event.type}': ${messages}`); + } + } + + function toInvokeErrorEvent( + state: AgentState, + error: unknown + ): JournalEvent { + return { + type: `xstate.error.invoke.${state.value}`, + error: serializeError(error), + at: Date.now(), + }; + } + + function validateEmittedPart(part: EmittedPart): void { + const schema = findEmittedSchema(cfg, part.type); + if (!schema) { + return; + } + + const result = schema['~standard'].validate(part); + if (result instanceof Promise) { + throw new Error( + 'Async schema validation is not supported in sync context.' + ); + } + + if (result.issues) { + const messages = formatSchemaIssues(result.issues); + throw new Error(`Invalid emitted part '${part.type}': ${messages}`); + } + } + + function createEnqueue(onEmit?: (part: EmittedPart) => void) { + return { + emit(part: EmittedPart) { + validateEmittedPart(part); + onEmit?.(part); + }, + }; + } + + async function createInvokeEvent( + state: AgentState, + sc: StateConfigAny, + onEmit?: (part: EmittedPart) => void + ): Promise { + try { + const result = await sc.invoke!( + { + context: state.context, + messages: state.messages, + input: getInput(state.value, state.input), + }, + createEnqueue(onEmit) + ); + const validatedResult = validateReplayableResult(state.value, result); + + return { + type: `xstate.done.invoke.${state.value}`, + output: validatedResult, + at: Date.now(), + }; + } catch (error) { + return { + type: `xstate.error.invoke.${state.value}`, + error: serializeError(error), + at: Date.now(), + }; + } + } + + async function createGenerateEvent(state: AgentState): Promise { + const sc = resolveStateConfig(cfg, state.value); + const adapter = sc.adapter ?? cfg.adapter; + if (!adapter?.generateText) { + return { + type: `xstate.error.invoke.${state.value}`, + error: { message: `No generateText adapter for '${state.value}'` }, + at: Date.now(), + }; + } + + try { + const messages = state.prompt + ? state.messages.concat({ role: 'user', content: state.prompt }) + : state.messages; + const result = await adapter.generateText({ + model: state.model, + system: state.system, + prompt: state.prompt, + messages, + tools: state.tools, + toolChoice: state.toolChoice, + outputSchema: sc.schemas?.output, + }); + const validatedResult = validateReplayableResult(state.value, result); + + return { + type: `xstate.done.invoke.${state.value}`, + output: validatedResult, + at: Date.now(), + }; + } catch (error) { + return { + type: `xstate.error.invoke.${state.value}`, + error: serializeError(error), + at: Date.now(), + }; + } + } + + async function getEffectEvent( + state: AgentState, + onEmit?: (part: EmittedPart) => void + ): Promise { + if (state.status === 'done' || state.status === 'error') { + return null; + } + + const sc = resolveStateConfig(cfg, state.value); + if (sc.always) { + return { + type: `xstate.always.${state.value}`, + at: Date.now(), + }; + } + + if (sc.invoke) { + return createInvokeEvent(state, sc, onEmit); + } + + if ( + sc.onDone + && ( + sc.prompt !== undefined + || sc.system !== undefined + || sc.tools !== undefined + || sc.toolChoice !== undefined + ) + ) { + return createGenerateEvent(state); + } + + return null; + } + + function resolveEffectTransition( + state: AgentState, + effectEvent: JournalEvent, + onEmit?: (part: EmittedPart) => void + ): { event: JournalEvent; next: AgentState } { + try { + return { + event: effectEvent, + next: transitionWithEffects(state, effectEvent, onEmit).next, + }; + } catch (error) { + if (isDoneInvokeEventType(state.value, effectEvent.type)) { + const errorEvent = toInvokeErrorEvent(state, error); + + return { + event: errorEvent, + next: transitionWithEffects(state, errorEvent, onEmit).next, + }; + } + + throw error; + } + } + + async function invoke(state: AgentState): Promise { + if (state.status === 'done' || state.status === 'error') { + return state; + } + + const sc = resolveStateConfig(cfg, state.value); + + if (sc.type === 'final') { + const rawOutput = sc.output + ? sc.output({ context: state.context, messages: state.messages }) + : undefined; + const output = cfg.schemas?.output + ? validateSchemaSync(cfg.schemas.output, rawOutput) + : rawOutput; + return resolveStateFields({ ...withoutResolvedFields(state), status: 'done', output }); + } + + const effectEvent = await getEffectEvent(state); + if (effectEvent) { + return resolveEffectTransition(state, effectEvent).next; + } + + if (sc.on) { + return resolveStateFields({ ...withoutResolvedFields(state), status: 'pending' }); + } + + return resolveStateFields({ + ...withoutResolvedFields(state), + status: 'error', + error: `State '${state.value}' has no invoke, events, or final type`, + }); + } + + async function execute(state: AgentState): Promise { + let current = state; + while (current.status === 'active') { + current = await invoke(current); + } + + switch (current.status) { + case 'done': + return { + status: 'done', + state: current, + output: current.output, + context: current.context, + messages: current.messages, + }; + case 'pending': + return { + status: 'pending', + state: current, + value: current.value, + events: getAvailableEvents(cfg, current.value), + context: current.context, + messages: current.messages, + }; + case 'error': + return { + status: 'error', + state: current, + error: current.error, + }; + default: + return { + status: 'error', + state: current, + error: `Unexpected: ${current.status}`, + }; + } + } + + async function* stream( + state: AgentState + ): AsyncGenerator { + let current = state; + const runtime = createSnapshotRuntime(current); + current = withRuntimeMetadata(current, runtime); + yield toSnap(current, runtime); + while (current.status === 'active') { + current = await invoke(current); + current = withRuntimeMetadata(current, runtime); + yield toSnap(current, runtime); + } + } + + function toSnap( + s: AgentState, + runtime: { sessionId: string; createdAt: number } + ): AgentSnapshot { + return { + value: s.value, + context: s.context, + messages: s.messages, + status: s.status, + sessionId: runtime.sessionId, + createdAt: runtime.createdAt, + input: s.input, + output: s.output, + error: s.error, + }; + } + + return { + id: cfg.id, + __config: cfg, + getInitialState, + resolveState, + transition, + invoke, + execute, + stream, + __runtime: { + toSnapshot: toSnap, + withRuntimeMetadata, + getEffectEvent, + resolveEffectTransition, + transitionWithEffects, + }, + } as AgentMachine; +} diff --git a/src/memory.ts b/src/memory.ts deleted file mode 100644 index a6994ab..0000000 --- a/src/memory.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AgentMemory, AgentMemoryContext } from './types'; - -export function createAgentMemory(): AgentMemory { - const storage = { - sessions: {} as Record, - }; - - return { - append: async (sessionId, key, item) => { - storage.sessions[sessionId] = - storage.sessions[sessionId] || - ({ - observations: [], - messages: [], - plans: [], - feedback: [], - } satisfies AgentMemoryContext); - - storage.sessions[sessionId]![key].push(item as any); - }, - getAll: async (sessionId, key) => { - return storage.sessions[sessionId]?.[key]; - }, - }; -} diff --git a/src/middleware.ts b/src/middleware.ts deleted file mode 100644 index ff1713c..0000000 --- a/src/middleware.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - Experimental_LanguageModelV1Middleware as LanguageModelV1Middleware, - LanguageModelV1StreamPart, -} from 'ai'; -import { - AnyAgent, - LanguageModelV1TextPart, - LanguageModelV1ToolCallPart, -} from './types'; -import { randomId } from './utils'; - -export function createAgentMiddleware(agent: AnyAgent) { - const middleware: LanguageModelV1Middleware = { - transformParams: async ({ params }) => { - return params; - }, - wrapGenerate: async ({ doGenerate, params }) => { - const id = randomId(); - - params.prompt.forEach((p) => { - agent.addMessage({ - id, - ...p, - timestamp: Date.now(), - correlationId: params.providerMetadata - ?.correlationId as unknown as string, - parentCorrelationId: params.providerMetadata - ?.parentCorrelationId as unknown as string, - }); - }); - - const result = await doGenerate(); - - return result; - }, - - wrapStream: async ({ doStream, params }) => { - const id = randomId(); - - params.prompt.forEach((message) => { - message.content; - agent.addMessage({ - id, - ...message, - timestamp: Date.now(), - correlationId: params.providerMetadata - ?.correlationId as unknown as string, - parentCorrelationId: params.providerMetadata - ?.parentCorrelationId as unknown as string, - }); - }); - - const { stream, ...rest } = await doStream(); - - let generatedText = ''; - - const transformStream = new TransformStream< - LanguageModelV1StreamPart, - LanguageModelV1StreamPart - >({ - transform(chunk, controller) { - if (chunk.type === 'text-delta') { - generatedText += chunk.textDelta; - } - - controller.enqueue(chunk); - }, - - flush() { - const content: ( - | LanguageModelV1TextPart - | LanguageModelV1ToolCallPart - )[] = []; - - if (generatedText) { - content.push({ - type: 'text', - text: generatedText, - }); - } - - agent.addMessage({ - id: randomId(), - timestamp: Date.now(), - role: 'assistant', - content, - responseId: id, - correlationId: params.providerMetadata - ?.correlationId as unknown as string, - parentCorrelationId: params.providerMetadata - ?.parentCorrelationId as unknown as string, - }); - }, - }); - - return { - stream: stream.pipeThrough(transformStream), - ...rest, - }; - }, - }; - return middleware; -} diff --git a/src/mockModel.ts b/src/mockModel.ts deleted file mode 100644 index 653bd38..0000000 --- a/src/mockModel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { LanguageModelV1 } from 'ai'; - -export class MockLanguageModelV1 implements LanguageModelV1 { - readonly specificationVersion = 'v1'; - - readonly provider: LanguageModelV1['provider']; - readonly modelId: LanguageModelV1['modelId']; - - doGenerate: LanguageModelV1['doGenerate']; - doStream: LanguageModelV1['doStream']; - - readonly defaultObjectGenerationMode: LanguageModelV1['defaultObjectGenerationMode']; - readonly supportsStructuredOutputs: LanguageModelV1['supportsStructuredOutputs']; - constructor({ - provider = 'mock-provider', - modelId = 'mock-model-id', - doGenerate = notImplemented, - doStream = notImplemented, - defaultObjectGenerationMode = undefined, - supportsStructuredOutputs = undefined, - }: { - provider?: LanguageModelV1['provider']; - modelId?: LanguageModelV1['modelId']; - doGenerate?: LanguageModelV1['doGenerate']; - doStream?: LanguageModelV1['doStream']; - defaultObjectGenerationMode?: LanguageModelV1['defaultObjectGenerationMode']; - supportsStructuredOutputs?: LanguageModelV1['supportsStructuredOutputs']; - } = {}) { - this.provider = provider; - this.modelId = modelId; - this.doGenerate = doGenerate; - this.doStream = doStream; - - this.defaultObjectGenerationMode = defaultObjectGenerationMode; - this.supportsStructuredOutputs = supportsStructuredOutputs; - } -} - -function notImplemented(): never { - throw new Error('Not implemented'); -} - -export const dummyResponseValues = { - rawCall: { rawPrompt: 'prompt', rawSettings: {} }, - finishReason: 'stop' as const, - usage: { promptTokens: 10, completionTokens: 20 }, -}; diff --git a/src/next/index.test.ts b/src/next/index.test.ts new file mode 100644 index 0000000..37984f4 --- /dev/null +++ b/src/next/index.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; +import { + createNextSessionRouteHandlers, + dynamic, + maxDuration, + runtime, +} from './index.js'; + +describe('next adapter', () => { + test('adapts generic session handlers to App Router route params', async () => { + const machine = createAgentMachine({ + id: 'next-adapter-test', + schemas: { + input: z.object({ + request: z.string(), + }), + events: { + approve: z.object({}), + }, + }, + context: (input) => ({ + request: input.request, + approved: false, + }), + initial: 'review', + states: { + review: { + on: { + approve: { + target: 'done', + context: { + approved: true, + }, + }, + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + request: context.request, + approved: context.approved, + }), + }, + }, + }); + const routes = createNextSessionRouteHandlers(machine); + + expect(runtime).toBe('nodejs'); + expect(dynamic).toBe('force-dynamic'); + expect(maxDuration).toBe(30); + + const startResponse = await routes.sessions.POST( + new Request('https://agent.test/api/sessions', { + method: 'POST', + body: JSON.stringify({ request: 'Ship it.' }), + }) + ); + const startBody = await startResponse.json() as { + sessionId: string; + }; + + const sendResponse = await routes.events.POST( + new Request(`https://agent.test/api/sessions/${startBody.sessionId}/events`, { + method: 'POST', + body: JSON.stringify({ type: 'approve' }), + }), + { + params: Promise.resolve({ + sessionId: startBody.sessionId, + }), + } + ); + const sendBody = await sendResponse.json() as { + snapshot: { value: string; output: unknown }; + }; + + expect(sendBody.snapshot).toEqual( + expect.objectContaining({ + value: 'done', + output: { + request: 'Ship it.', + approved: true, + }, + }) + ); + }); +}); diff --git a/src/next/index.ts b/src/next/index.ts new file mode 100644 index 0000000..6fa6431 --- /dev/null +++ b/src/next/index.ts @@ -0,0 +1,81 @@ +import { + createSessionHttpController, + type SessionHttpController, + type SessionHttpControllerOptions, +} from '../http/index.js'; +import type { AgentMachine } from '../types.js'; + +type AnyMachine = AgentMachine; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; +export const maxDuration = 30; + +export interface NextRouteContext> { + params: Promise | TParams; +} + +export interface NextSessionRouteHandlers { + sessions: { + POST(request: Request): Promise; + }; + session: { + GET( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + events: { + POST( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + stream: { + GET( + request: Request, + context: NextRouteContext<{ sessionId: string }> + ): Promise; + }; + controller: SessionHttpController; +} + +export function createNextSessionRouteHandlers( + machine: TMachine, + options: SessionHttpControllerOptions = {} +): NextSessionRouteHandlers { + const controller = createSessionHttpController(machine, options); + + return { + sessions: { + POST(request) { + return controller.handle(rewritePath(request, '/sessions')); + }, + }, + session: { + async GET(request, context) { + const { sessionId } = await context.params; + return controller.handle(rewritePath(request, `/sessions/${sessionId}`)); + }, + }, + events: { + async POST(request, context) { + const { sessionId } = await context.params; + return controller.handle(rewritePath(request, `/sessions/${sessionId}/events`)); + }, + }, + stream: { + async GET(request, context) { + const { sessionId } = await context.params; + return controller.handle(rewritePath(request, `/sessions/${sessionId}/stream`)); + }, + }, + controller, + }; +} + +function rewritePath(request: Request, pathname: string): Request { + const url = new URL(request.url); + url.pathname = pathname; + return new Request(url, request); +} diff --git a/src/persistence.test.ts b/src/persistence.test.ts new file mode 100644 index 0000000..7cd2f17 --- /dev/null +++ b/src/persistence.test.ts @@ -0,0 +1,146 @@ +import { expect, test } from 'vitest'; +import { createMemoryRunStore } from './index.js'; + +test('appends and loads journal events in sequence order', async () => { + const store = createMemoryRunStore(); + + const first = await store.append('session-1', { + type: 'xstate.done.invoke.worker', + at: 20, + }); + + const second = await store.append('session-1', { + type: 'xstate.init', + at: 10, + }); + + expect(first.sequence).toBe(1); + expect(second.sequence).toBe(2); + + expect(await store.loadEvents('session-1')).toEqual([ + { + sequence: 1, + type: 'xstate.done.invoke.worker', + at: 20, + }, + { + sequence: 2, + type: 'xstate.init', + at: 10, + }, + ]); + + expect(await store.loadEvents('session-1', 1)).toEqual([ + { + sequence: 2, + at: 10, + type: 'xstate.init', + }, + ]); +}); + +test('loads the most replay-advanced saved snapshot', async () => { + const store = createMemoryRunStore(); + + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 1, + snapshot: { + value: 'idle', + context: { count: 1 }, + messages: [], + status: 'active', + createdAt: 100, + sessionId: 'session-1', + input: { + idle: { count: 1 }, + }, + }, + createdAt: 100, + }); + + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 3, + snapshot: { + value: 'done', + context: { count: 2 }, + messages: [], + status: 'done', + createdAt: 300, + sessionId: 'session-1', + input: { + done: { count: 2 }, + }, + output: { count: 2 }, + }, + createdAt: 300, + }); + + expect(await store.loadLatestSnapshot('session-1')).toEqual({ + sessionId: 'session-1', + afterSequence: 3, + snapshot: { + value: 'done', + context: { count: 2 }, + messages: [], + status: 'done', + createdAt: 300, + sessionId: 'session-1', + input: { + done: { count: 2 }, + }, + output: { count: 2 }, + }, + createdAt: 300, + }); +}); + +test('loads the most replay-advanced snapshot even if saved earlier', async () => { + const store = createMemoryRunStore(); + + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 5, + snapshot: { + value: 'done', + context: { count: 5 }, + messages: [], + status: 'done', + createdAt: 500, + sessionId: 'session-1', + input: { done: { count: 5 } }, + }, + createdAt: 500, + }); + + await store.saveSnapshot({ + sessionId: 'session-1', + afterSequence: 2, + snapshot: { + value: 'review', + context: { count: 2 }, + messages: [], + status: 'active', + createdAt: 200, + sessionId: 'session-1', + input: { review: { count: 2 } }, + }, + createdAt: 200, + }); + + expect(await store.loadLatestSnapshot('session-1')).toEqual({ + sessionId: 'session-1', + afterSequence: 5, + snapshot: { + value: 'done', + context: { count: 5 }, + messages: [], + status: 'done', + createdAt: 500, + sessionId: 'session-1', + input: { done: { count: 5 } }, + }, + createdAt: 500, + }); +}); diff --git a/src/planners/shortestPathPlanner.ts b/src/planners/shortestPathPlanner.ts deleted file mode 100644 index 3abc85c..0000000 --- a/src/planners/shortestPathPlanner.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AgentPlan, AgentPlanInput, AnyAgent } from '../types'; -import { getShortestPaths } from '@xstate/graph'; - -export async function simplePlanner( - agent: T, - input: AgentPlanInput -): Promise | undefined> { - // 1. Determine goal state criteria - // e.g. a state where the agent has won a game - void 0; - - // 2. Determine possible events that can occur - void 0; - - // 3. Get shortest paths from current state to - // a state matching the criteria, using - // possible events - void 0; - - // 4. Return shortest path as a plan - return null as any; -} diff --git a/src/planners/simplePlanner.ts b/src/planners/simplePlanner.ts deleted file mode 100644 index bb2de97..0000000 --- a/src/planners/simplePlanner.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { CoreMessage, type CoreTool, generateText, tool } from 'ai'; -import { - AgentPlan, - AgentPlanInput, - ObservedState, - PromptTemplate, - TransitionData, - AnyAgent, -} from '../types'; -import { getAllTransitions, randomId } from '../utils'; -import { AnyStateMachine } from 'xstate'; -import { defaultTextTemplate } from '../templates/defaultText'; -import { getMessages } from '../text'; - -function getTransitions( - state: ObservedState, - machine: AnyStateMachine -): TransitionData[] { - if (!machine) { - return []; - } - - const resolvedState = machine.resolveState(state); - return getAllTransitions(resolvedState); -} - -const simplePlannerPromptTemplate: PromptTemplate = (data) => { - return ` -${defaultTextTemplate(data)} - -Make at most one tool call to achieve the above goal. If the goal cannot be achieved with any tool calls, do not make any tool call. - `.trim(); -}; - -export async function simplePlanner( - agent: T, - input: AgentPlanInput -): Promise | undefined> { - // Get all of the possible next transitions - const transitions: TransitionData[] = input.machine - ? getTransitions(input.state, input.machine) - : Object.entries(input.events).map(([eventType, { description }]) => ({ - eventType, - description, - })); - - // Only keep the transitions that match the event types that are in the event mapping - // TODO: allow for custom filters - const filter = (eventType: string) => - Object.keys(input.events).includes(eventType); - - // Mapping of each event type (e.g. "mouse.click") - // to a valid function name (e.g. "mouse_click") - const functionNameMapping: Record = {}; - - const toolTransitions = transitions - .filter((t) => { - return filter(t.eventType); - }) - .map((t) => { - const name = t.eventType.replace(/\./g, '_'); - functionNameMapping[name] = t.eventType; - - return { - type: 'function', - eventType: t.eventType, - description: t.description, - name, - } as const; - }); - - // Convert the transition data to a tool map that the - // Vercel AI SDK can use - const toolMap: Record> = {}; - for (const toolTransitionData of toolTransitions) { - const toolZodType = input.events?.[toolTransitionData.eventType]; - - if (!toolZodType) { - continue; - } - - toolMap[toolTransitionData.name] = tool({ - description: toolZodType?.description ?? toolTransitionData.description, - parameters: toolZodType, - execute: async (params: Record) => { - const event = { - type: toolTransitionData.eventType, - ...params, - }; - - return event; - }, - }); - } - - if (!Object.keys(toolMap).length) { - // No valid transitions for the specified tools - return undefined; - } - - // Create a prompt with the given context and goal. - // The template is used to ensure that a single tool call at most is made. - const prompt = simplePlannerPromptTemplate({ - context: input.state.context, - goal: input.goal, - }); - - const messages = await getMessages(agent, prompt, input); - - const model = input.model ? agent.wrap(input.model) : agent.model; - - const { - state, - machine, - previousPlan, - events, - goal, - model: _, - ...rest - } = input; - - const result = await generateText({ - // ...input, - ...rest, - model, - messages, - tools: toolMap as any, - toolChoice: input.toolChoice ?? 'required', - }); - - result.responseMessages.forEach((m) => { - const message: CoreMessage = m; - - agent.addMessage({ - ...message, - id: randomId(), - timestamp: Date.now(), - }); - }); - - const singleResult = result.toolResults[0]; - - if (!singleResult) { - // TODO: retries? - console.warn('No tool call results returned'); - return undefined; - } - - return { - goal: input.goal, - state: input.state, - execute: async (state) => { - if (JSON.stringify(state) === JSON.stringify(input.state)) { - return singleResult.result; - } - return undefined; - }, - nextEvent: singleResult.result, - sessionId: agent.sessionId, - timestamp: Date.now(), - }; -} diff --git a/src/restore.test.ts b/src/restore.test.ts new file mode 100644 index 0000000..b840845 --- /dev/null +++ b/src/restore.test.ts @@ -0,0 +1,77 @@ +import { expect, test, vi } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + restoreSession, + startSession, +} from './index.js'; + +test('restoreSession reconstructs from the latest snapshot plus replay tail', async () => { + const machine = createAgentMachine({ + id: 'restore-session', + context: () => ({ approved: false, result: null as string | null }), + initial: 'review', + states: { + review: { + on: { + approve: { + target: 'processing', + context: { approved: true }, + }, + }, + }, + processing: { + schemas: { output: z.object({ value: z.string() }) }, + invoke: async ({ context }) => ({ + value: context.approved ? 'approved' : 'rejected', + }), + onDone: ({ output }) => ({ + target: 'done', + context: { result: output.value }, + }), + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const baseStore = createMemoryRunStore(); + let snapshotWrites = 0; + const store = { + append: baseStore.append, + loadEvents: baseStore.loadEvents, + loadLatestSnapshot: baseStore.loadLatestSnapshot, + async saveSnapshot(snapshot: Awaited< + ReturnType + > extends infer TSaved + ? Exclude + : never) { + snapshotWrites += 1; + if (snapshotWrites === 1) { + await baseStore.saveSnapshot(snapshot); + } + }, + }; + + const liveRun = await startSession(machine, { store }); + await liveRun.send({ type: 'approve' }); + + expect(await store.loadLatestSnapshot(liveRun.sessionId)).toEqual( + expect.objectContaining({ + afterSequence: 1, + }) + ); + + const restoredRun = await restoreSession(machine, { + sessionId: liveRun.sessionId, + store, + }); + await vi.waitFor(() => { + expect(restoredRun.getSnapshot()).toEqual(liveRun.getSnapshot()); + }); + + expect(restoredRun.getSnapshot()).toEqual(liveRun.getSnapshot()); +}); diff --git a/src/runtime/emitter.ts b/src/runtime/emitter.ts new file mode 100644 index 0000000..4e9f2d5 --- /dev/null +++ b/src/runtime/emitter.ts @@ -0,0 +1,36 @@ +type Handler = (event: unknown) => void; + +export interface RunEmitter { + emit(type: string, event: unknown): void; + on(type: string, handler: Handler): () => void; +} + +export function createRunEmitter(): RunEmitter { + const listeners = new Map>(); + + return { + emit(type, event) { + for (const handler of listeners.get(type) ?? []) { + handler(event); + } + }, + + on(type, handler) { + const current = listeners.get(type) ?? new Set(); + current.add(handler); + listeners.set(type, current); + + return () => { + const active = listeners.get(type); + if (!active) { + return; + } + + active.delete(handler); + if (active.size === 0) { + listeners.delete(type); + } + }; + }, + }; +} diff --git a/src/runtime/events.ts b/src/runtime/events.ts new file mode 100644 index 0000000..ee30061 --- /dev/null +++ b/src/runtime/events.ts @@ -0,0 +1,7 @@ +export interface JournalEvent { + type: 'xstate.init' | (string & {}); + at: number; + sessionId?: string; + sequence?: number; + [key: string]: unknown; +} diff --git a/src/runtime/index.test.ts b/src/runtime/index.test.ts new file mode 100644 index 0000000..10f1569 --- /dev/null +++ b/src/runtime/index.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from '../index.js'; +import { waitForRunDone, waitForRunSnapshot } from './index.js'; + +describe('runtime helpers', () => { + test('waitForRunSnapshot and waitForRunDone observe session lifecycle', async () => { + const machine = createAgentMachine({ + id: 'runtime-helper-test', + schemas: { + events: { + finish: z.object({ value: z.string() }), + }, + }, + context: () => ({ + value: null as string | null, + }), + initial: 'waiting', + states: { + waiting: { + on: { + finish: ({ event }) => ({ + target: 'done', + context: { + value: event.value, + }, + }), + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + value: context.value, + }), + }, + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + const waiting = await waitForRunSnapshot( + run, + (snapshot) => snapshot.status === 'pending' + ); + + expect(waiting.value).toBe('waiting'); + + const donePromise = waitForRunDone(run); + await run.send({ type: 'finish', value: 'ok' }); + + await expect(donePromise).resolves.toEqual( + expect.objectContaining({ + output: { + value: 'ok', + }, + }) + ); + }); +}); diff --git a/src/runtime/index.ts b/src/runtime/index.ts new file mode 100644 index 0000000..0233285 --- /dev/null +++ b/src/runtime/index.ts @@ -0,0 +1,80 @@ +import type { + AgentRun, + AgentSnapshot, + StandardSchemaV1, +} from '../types.js'; + +export function waitForRunDone< + TContext extends Record, + TValue extends string, + TEvents extends Record, + TOutput, + TEmitted extends Record, +>( + run: AgentRun +): Promise<{ + output: TOutput; + snapshot: AgentSnapshot; +}> { + return new Promise((resolve, reject) => { + const offDone = run.onDone((event) => { + offDone(); + offError(); + resolve(event); + }); + const offError = run.onError((event) => { + offDone(); + offError(); + reject(event.error); + }); + }); +} + +export function waitForRunSnapshot< + TContext extends Record, + TValue extends string, + TEvents extends Record, + TOutput, + TEmitted extends Record, +>( + run: AgentRun, + predicate: ( + snapshot: AgentSnapshot + ) => boolean, + timeoutMs = 1000 +): Promise> { + const current = run.getSnapshot(); + if (predicate(current)) { + return Promise.resolve(current); + } + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + cleanup(); + reject(new Error('Run snapshot did not reach the expected state in time.')); + }, timeoutMs); + + const cleanup = () => { + clearTimeout(timeout); + offSnapshot(); + offDone(); + offError(); + }; + + const check = (snapshot: AgentSnapshot) => { + if (predicate(snapshot)) { + cleanup(); + resolve(snapshot); + } + }; + + const offSnapshot = run.onSnapshot(check); + const offDone = run.onDone((event) => { + check(event.snapshot); + }); + const offError = run.onError((event) => { + cleanup(); + reject(event.error); + }); + }); +} diff --git a/src/runtime/memory-store.ts b/src/runtime/memory-store.ts new file mode 100644 index 0000000..f2a6961 --- /dev/null +++ b/src/runtime/memory-store.ts @@ -0,0 +1,55 @@ +import type { AgentSnapshot } from '../types.js'; +import type { JournalEvent } from './events.js'; +import type { + JournalEventRecord, + PersistedSnapshot, + RunStore, +} from './store.js'; + +function compareSnapshots( + a: PersistedSnapshot, + b: PersistedSnapshot +): number { + return a.afterSequence - b.afterSequence || a.createdAt - b.createdAt; +} + +export function createMemoryRunStore< + TSnapshot extends AgentSnapshot = AgentSnapshot, + TEvent extends JournalEvent = JournalEvent, +>(): RunStore { + const journals = new Map>>(); + const snapshots = new Map[]>(); + + return { + async append(sessionId, event) { + const current = journals.get(sessionId) ?? []; + const sequence = current.length === 0 ? 1 : current[current.length - 1]!.sequence + 1; + current.push({ ...event, sequence }); + journals.set(sessionId, current); + return { sequence }; + }, + + async loadEvents(sessionId, afterSequence = 0) { + const events = journals.get(sessionId) ?? []; + return [...events] + .filter((entry) => entry.sequence > afterSequence) + .sort((a, b) => a.sequence - b.sequence); + }, + + async loadLatestSnapshot(sessionId) { + const saved = snapshots.get(sessionId); + if (!saved?.length) { + return null; + } + + const sorted = [...saved].sort(compareSnapshots); + return sorted[sorted.length - 1] ?? null; + }, + + async saveSnapshot(snapshot) { + const current = snapshots.get(snapshot.sessionId) ?? []; + current.push(snapshot); + snapshots.set(snapshot.sessionId, current); + }, + }; +} diff --git a/src/runtime/session.ts b/src/runtime/session.ts new file mode 100644 index 0000000..bb208f5 --- /dev/null +++ b/src/runtime/session.ts @@ -0,0 +1,445 @@ +import type { JournalEvent } from './events.js'; +import { createRunEmitter } from './emitter.js'; +import type { + AgentMachine, + AgentRun, + AgentSnapshot, + AgentState, + EmittedPart, + RestoreSessionOptions, + SessionOptions, +} from '../types.js'; +import { isReservedInternalEventType } from '../utils.js'; + +const RESERVED_PUBLIC_ON_TYPES = new Set([ + 'part', + 'done', + 'error', + 'state', + 'machine.event', + 'runtime', +]); + +type SnapshotRuntime = { + sessionId: string; + createdAt: number; +}; + +type RuntimeMachine = AgentMachine & { + __runtime: { + toSnapshot(state: AgentState, runtime: SnapshotRuntime): AgentSnapshot; + withRuntimeMetadata(state: AgentState, runtime: SnapshotRuntime): AgentState; + getEffectEvent( + state: AgentState, + onEmit?: (part: EmittedPart) => void + ): Promise; + resolveEffectTransition( + state: AgentState, + effectEvent: JournalEvent, + onEmit?: (part: EmittedPart) => void + ): { event: JournalEvent; next: AgentState }; + transitionWithEffects( + state: AgentState, + event: { type: string; [key: string]: unknown }, + onEmit?: (part: EmittedPart) => void + ): { next: AgentState; emitted: EmittedPart[] }; + }; +}; + +type RunState = { + current: AgentState; + snapshot: AgentSnapshot; + lastSequence: number; + runtime: SnapshotRuntime; +}; + +function createSessionId(): string { + if ( + typeof globalThis.crypto !== 'undefined' + && typeof globalThis.crypto.randomUUID === 'function' + ) { + return globalThis.crypto.randomUUID(); + } + + return `session-${Math.random().toString(36).slice(2)}`; +} + +function asRuntimeMachine(machine: AgentMachine): RuntimeMachine { + const runtimeMachine = machine as RuntimeMachine; + if (!runtimeMachine.__runtime) { + throw new Error('Machine runtime internals are unavailable'); + } + + return runtimeMachine; +} + +function toJournalEvent( + event: { type: string; [key: string]: unknown } +): JournalEvent { + return { + ...event, + at: typeof event.at === 'number' ? event.at : Date.now(), + }; +} + +function createRun( + machine: AgentMachine, + store: SessionOptions['store'], + runtimeMachine: RuntimeMachine, + runState: RunState, + emitter = createRunEmitter() +): AgentRun { + let releaseStart!: () => void; + let operation = new Promise((resolve) => { + releaseStart = resolve; + }); + let startScheduled = false; + let terminalEmitted = false; + + function emitPart(part: EmittedPart) { + emitter.emit('part', part); + emitter.emit(part.type, part); + } + + function enqueue(op: () => Promise): Promise { + const result = operation.then(op); + operation = result.then( + () => undefined, + () => undefined + ); + + return result; + } + + function emitTerminalIfNeeded() { + if (terminalEmitted) { + return; + } + + if (runState.snapshot.status === 'done') { + terminalEmitted = true; + emitter.emit('runtime', { + type: 'session.completed', + sessionId: runState.runtime.sessionId, + at: Date.now(), + }); + emitter.emit('done', { + output: runState.snapshot.output, + snapshot: runState.snapshot, + }); + return; + } + + if (runState.snapshot.status === 'error') { + terminalEmitted = true; + emitter.emit('runtime', { + type: 'session.failed', + sessionId: runState.runtime.sessionId, + error: runState.snapshot.error, + at: Date.now(), + }); + emitter.emit('error', { + error: runState.snapshot.error, + snapshot: runState.snapshot, + }); + } + } + + async function persistSnapshot() { + runState.snapshot = runtimeMachine.__runtime.toSnapshot( + runState.current, + runState.runtime + ); + + await store.saveSnapshot({ + sessionId: runState.runtime.sessionId, + afterSequence: runState.lastSequence, + snapshot: runState.snapshot, + createdAt: Date.now(), + }); + + emitter.emit('runtime', { + type: 'snapshot.persisted', + sessionId: runState.runtime.sessionId, + afterSequence: runState.lastSequence, + at: Date.now(), + }); + emitter.emit('state', runState.snapshot); + emitTerminalIfNeeded(); + } + + async function appendMachineEvent(event: JournalEvent) { + const record = await store.append(runState.runtime.sessionId, event); + runState.lastSequence = record.sequence; + emitter.emit('machine.event', { + ...event, + sequence: record.sequence, + }); + } + + async function settle() { + while (runState.current.status === 'active') { + const effectEvent = await runtimeMachine.__runtime.getEffectEvent( + runState.current, + emitPart + ); + + if (effectEvent) { + const resolved = runtimeMachine.__runtime.resolveEffectTransition( + runState.current, + effectEvent, + emitPart + ); + + await appendMachineEvent(resolved.event); + runState.current = runtimeMachine.__runtime.withRuntimeMetadata( + resolved.next, + runState.runtime + ); + await persistSnapshot(); + continue; + } + + runState.current = runtimeMachine.__runtime.withRuntimeMetadata( + await machine.invoke(runState.current), + runState.runtime + ); + await persistSnapshot(); + } + } + + function scheduleStart() { + if (startScheduled) { + return; + } + + startScheduled = true; + void enqueue(async () => { + await settle(); + }); + queueMicrotask(() => { + releaseStart(); + }); + } + + return { + get sessionId() { + return runState.runtime.sessionId; + }, + + get status() { + return runState.snapshot.status; + }, + + getSnapshot() { + return runState.snapshot; + }, + + async send(event) { + if (isReservedInternalEventType(event.type)) { + throw new Error( + `Cannot send reserved internal event '${event.type}' to a session` + ); + } + + return enqueue(async () => { + const journalEvent = toJournalEvent(event); + const next = runtimeMachine.__runtime.transitionWithEffects( + runState.current, + journalEvent, + emitPart + ).next; + + await appendMachineEvent(journalEvent); + runState.current = runtimeMachine.__runtime.withRuntimeMetadata( + next, + runState.runtime + ); + await persistSnapshot(); + await settle(); + }); + }, + + on(type, handler) { + if (RESERVED_PUBLIC_ON_TYPES.has(type)) { + throw new Error( + `'${type}' is not an emitted event subscription. Use a dedicated run method instead.` + ); + } + + return emitter.on(type, handler as (event: unknown) => void); + }, + + onEmitted(handler) { + return emitter.on('part', handler as (event: unknown) => void); + }, + + onDone(handler) { + return emitter.on('done', handler as (event: unknown) => void); + }, + + onError(handler) { + return emitter.on('error', handler as (event: unknown) => void); + }, + + onSnapshot(handler) { + return emitter.on('state', handler as (event: unknown) => void); + }, + + onMachineEvent(handler) { + return emitter.on('machine.event', handler as (event: unknown) => void); + }, + + /** @internal */ + async __persistCurrent() { + await persistSnapshot(); + }, + + /** @internal */ + async __settle() { + await enqueue(async () => { + await settle(); + }); + }, + + /** @internal */ + __scheduleStart() { + scheduleStart(); + }, + } as AgentRun; +} + +export async function startSession< + TInput, + TContext extends Record, + TEvents extends Record, + TStates extends Record, + TOutput, + TEmitted extends Record, +>( + machine: AgentMachine, + options: SessionOptions +): Promise> { + const runtimeMachine = asRuntimeMachine(machine); + const initialState = (machine as AgentMachine).getInitialState( + options.input as TInput + ) as AgentState; + const runtime = { + sessionId: options.sessionId ?? createSessionId(), + createdAt: Date.now(), + }; + const runState: RunState = { + current: runtimeMachine.__runtime.withRuntimeMetadata(initialState, runtime), + snapshot: runtimeMachine.__runtime.toSnapshot(initialState, runtime), + lastSequence: 0, + runtime, + }; + + const run = createRun( + machine, + options.store, + runtimeMachine, + runState + ) as AgentRun & { + __persistCurrent(): Promise; + __settle(): Promise; + __scheduleStart(): void; + }; + + const initEvent = { + type: 'xstate.init', + input: options.input, + at: runtime.createdAt, + } satisfies JournalEvent; + const record = await options.store.append(runtime.sessionId, initEvent); + runState.lastSequence = record.sequence; + + await run.__persistCurrent(); + run.__scheduleStart(); + + return run; +} + +export async function restoreSession< + TInput, + TContext extends Record, + TEvents extends Record, + TStates extends Record, + TOutput, + TEmitted extends Record, +>( + machine: AgentMachine, + options: RestoreSessionOptions +): Promise> { + const runtimeMachine = asRuntimeMachine(machine); + const persisted = await options.store.loadLatestSnapshot(options.sessionId); + const allEvents = await options.store.loadEvents(options.sessionId); + const initEvent = allEvents.find( + (event) => event.type === 'xstate.init' + ); + + if (!persisted && !initEvent) { + throw new Error(`No persisted session '${options.sessionId}' found`); + } + + const runtime = { + sessionId: options.sessionId, + createdAt: persisted?.snapshot.createdAt ?? initEvent?.at ?? Date.now(), + }; + const initialState = persisted + ? (machine.resolveState( + persisted.snapshot as AgentSnapshot + ) as AgentState) + : ((machine as AgentMachine).getInitialState(initEvent?.input) as AgentState< + TContext, + keyof TStates & string, + TOutput + >); + const runState: RunState = { + current: runtimeMachine.__runtime.withRuntimeMetadata(initialState, runtime), + snapshot: + persisted?.snapshot + ?? runtimeMachine.__runtime.toSnapshot(initialState, runtime), + lastSequence: persisted?.afterSequence ?? (initEvent?.sequence ?? 0), + runtime, + }; + const run = createRun( + machine, + options.store, + runtimeMachine, + runState + ) as AgentRun & { + __persistCurrent(): Promise; + __settle(): Promise; + __scheduleStart(): void; + }; + + const replayTail = await options.store.loadEvents( + options.sessionId, + runState.lastSequence + ); + let replayed = false; + + for (const event of replayTail) { + runState.current = runtimeMachine.__runtime.withRuntimeMetadata( + machine.transition( + runState.current as AgentState, + event as unknown as import('../types.js').TransitionEvent + ) as AgentState, + runState.runtime + ); + runState.lastSequence = event.sequence; + runState.snapshot = runtimeMachine.__runtime.toSnapshot( + runState.current, + runState.runtime + ); + replayed = true; + } + + if (!persisted || replayed) { + await run.__persistCurrent(); + } + run.__scheduleStart(); + + return run; +} diff --git a/src/runtime/store.ts b/src/runtime/store.ts new file mode 100644 index 0000000..4446165 --- /dev/null +++ b/src/runtime/store.ts @@ -0,0 +1,28 @@ +import type { AgentSnapshot } from '../types.js'; +import type { JournalEvent } from './events.js'; + +export type JournalEventRecord< + TEvent extends JournalEvent = JournalEvent, +> = TEvent & { sequence: number }; + +export interface PersistedSnapshot< + TSnapshot extends AgentSnapshot = AgentSnapshot, +> { + sessionId: string; + snapshot: TSnapshot; + afterSequence: number; + createdAt: number; +} + +export interface RunStore< + TSnapshot extends AgentSnapshot = AgentSnapshot, + TEvent extends JournalEvent = JournalEvent, +> { + append(sessionId: string, event: TEvent): Promise<{ sequence: number }>; + loadEvents( + sessionId: string, + afterSequence?: number + ): Promise[]>; + loadLatestSnapshot(sessionId: string): Promise | null>; + saveSnapshot(snapshot: PersistedSnapshot): Promise; +} diff --git a/src/schemas.ts b/src/schemas.ts deleted file mode 100644 index aefa24a..0000000 --- a/src/schemas.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ZodType, type SomeZodObject } from 'zod'; - -export type ZodEventMapping = { - // map event types to Zod types - [eventType: string]: SomeZodObject; -}; - -export type ZodContextMapping = { - // map context keys to Zod types - [contextKey: string]: ZodType; -}; diff --git a/src/session-runtime.test.ts b/src/session-runtime.test.ts new file mode 100644 index 0000000..8844a1f --- /dev/null +++ b/src/session-runtime.test.ts @@ -0,0 +1,230 @@ +import { expect, test, vi } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from './index.js'; + +function deferred() { + let resolve!: (value: T | PromiseLike) => void; + const promise = new Promise((nextResolve) => { + resolve = nextResolve; + }); + + return { promise, resolve }; +} + +test('startSession creates a session, persists xstate.init, and returns before start effects run', async () => { + const machine = createAgentMachine({ + id: 'session-runtime', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + increment: { + target: 'done', + context: { count: 1 }, + }, + }, + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + const snapshot = run.getSnapshot(); + const journal = await store.loadEvents(run.sessionId); + const persisted = await store.loadLatestSnapshot(run.sessionId); + + expect(run.sessionId).toBe(snapshot.sessionId); + expect(snapshot).toEqual( + expect.objectContaining({ + sessionId: run.sessionId, + value: 'idle', + status: 'active', + context: { count: 0 }, + input: {}, + }) + ); + await vi.waitFor(() => { + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'idle', + status: 'pending', + }) + ); + }); + expect(journal).toEqual([ + expect.objectContaining({ + sequence: 1, + type: 'xstate.init', + at: expect.any(Number), + }), + ]); + expect(persisted).toEqual( + expect.objectContaining({ + sessionId: run.sessionId, + afterSequence: 1, + snapshot, + }) + ); +}); + +test('serializes concurrent sends so each event applies from the latest snapshot', async () => { + const gates = [deferred(), deferred()]; + let invocations = 0; + const machine = createAgentMachine({ + id: 'serialized-send', + schemas: { + events: { + increment: z.object({ amount: z.number() }), + }, + }, + context: () => ({ count: 0 }), + initial: 'ready', + states: { + ready: { + on: { + increment: ({ event, context }) => ({ + target: 'working', + context: { count: context.count + event.amount }, + }), + }, + }, + working: { + schemas: { output: z.object({ count: z.number() }) }, + invoke: async ({ context }) => { + const gate = gates[invocations++]!; + await gate.promise; + return { count: context.count }; + }, + onDone: ({ output }) => ({ + target: 'ready', + context: { count: output.count }, + }), + }, + }, + }); + + const run = await startSession(machine, { store: createMemoryRunStore() }); + await vi.waitFor(() => { + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'ready', + status: 'pending', + }) + ); + }); + + const first = run.send({ type: 'increment', amount: 1 }); + const second = run.send({ type: 'increment', amount: 10 }); + + await vi.waitFor(() => { + expect(invocations).toBe(1); + }); + + gates[0]!.resolve(); + await first; + await vi.waitFor(() => { + expect(invocations).toBe(2); + }); + + gates[1]!.resolve(); + await second; + + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'ready', + status: 'pending', + context: { count: 11 }, + }) + ); +}); + +test('journals always transitions and persists messages', async () => { + const machine = createAgentMachine({ + id: 'always-session', + context: () => ({ ready: false }), + messages: () => [{ role: 'user', content: 'start' }], + initial: 'checking', + states: { + checking: { + always: ({ messages }) => ({ + target: 'done', + context: { ready: true }, + messages: messages.concat({ role: 'assistant', content: 'done' }), + }), + }, + done: { + type: 'final', + output: ({ context, messages }) => ({ ...context, messages }), + }, + }, + }); + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + + await vi.waitFor(() => { + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + context: { ready: true }, + messages: [ + { role: 'user', content: 'start' }, + { role: 'assistant', content: 'done' }, + ], + }) + ); + }); + + await expect(store.loadEvents(run.sessionId)).resolves.toEqual([ + expect.objectContaining({ sequence: 1, type: 'xstate.init' }), + expect.objectContaining({ sequence: 2, type: 'xstate.always.checking' }), + ]); +}); + +test('rejects reserved internal events from run.send', async () => { + const machine = createAgentMachine({ + id: 'reserved-events', + context: () => ({ count: 0 }), + initial: 'ready', + states: { + ready: { + on: { + go: { target: 'done' }, + }, + }, + done: { type: 'final' }, + }, + }); + + const run = await startSession(machine, { store: createMemoryRunStore() }); + await vi.waitFor(() => { + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'ready', + status: 'pending', + }) + ); + }); + + await expect(run.send({ type: 'xstate.init' })).rejects.toThrow( + /reserved internal event/i + ); + await expect( + run.send({ type: 'xstate.done.invoke.worker' }) + ).rejects.toThrow(/reserved internal event/i); + await expect( + run.send({ type: 'xstate.error.invoke.worker' }) + ).rejects.toThrow(/reserved internal event/i); + await expect( + run.send({ type: 'xstate.always.ready' }) + ).rejects.toThrow(/reserved internal event/i); +}); diff --git a/src/session-types.test.ts b/src/session-types.test.ts new file mode 100644 index 0000000..af88cc5 --- /dev/null +++ b/src/session-types.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest'; +import type { AgentSnapshot, JournalEvent } from './index.js'; + +test('AgentSnapshot includes durable session fields', () => { + const snapshot: AgentSnapshot<{ count: number }, 'idle'> = { + value: 'idle', + context: { count: 1 }, + messages: [], + status: 'active', + createdAt: 123, + sessionId: 'session-1', + input: {}, + }; + + expect(snapshot.sessionId).toBe('session-1'); + expect(snapshot.createdAt).toBe(123); +}); + +test('JournalEvent supports invoke completion events', () => { + const event: JournalEvent = { + type: 'xstate.done.invoke.worker', + at: 456, + }; + + expect(event.type).toBe('xstate.done.invoke.worker'); + expect(event.at).toBe(456); +}); diff --git a/src/strategies/chain-of-note.ts b/src/strategies/chain-of-note.ts deleted file mode 100644 index 877799c..0000000 --- a/src/strategies/chain-of-note.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { GenerateTextResult, LanguageModel } from 'ai'; -import wiki, { wikiSearchResult, wikiSummary } from 'wikipedia'; -import { assign, fromPromise, setup } from 'xstate'; -import { AnyAgent } from '../types'; - -const searchWiki = fromPromise( - async ({ - input, - }: { - input: { - query: string; - limit?: number; - }; - }) => { - const passages = await wiki.search(input.query, { - limit: input.limit ?? 5, - }); - return passages; - } -); - -const extractSummaries = fromPromise( - async ({ - input, - }: { - input: { - searchResult: wikiSearchResult; - }; - }) => { - const summaries = await Promise.all( - input.searchResult.results.map(async (result) => { - const summary = await wiki.summary(result.title); - return { - title: result.title, - summary, - }; - }) - ); - return summaries; - } -); - -export const chainOfNote = setup({ - types: { - input: {} as { - model: LanguageModel; - agent: AnyAgent; - prompt: string; - }, - context: {} as { - searchResults: wikiSearchResult | null; - summaries: - | { - title: any; - summary: wikiSummary; - }[] - | null; - model: LanguageModel; - agent: AnyAgent; - prompt: string; - }, - output: {} as GenerateTextResult, - }, - actors: { - searchWiki, - extractSummaries, - }, -}).createMachine({ - initial: 'searching', - context: ({ input }) => ({ - ...input, - searchResults: null, - summaries: null, - }), - states: { - searching: { - invoke: { - src: 'searchWiki', - input: ({ context }) => ({ - query: context.prompt, - }), - onDone: { - actions: assign({ - searchResults: ({ event }) => event.output, - }), - target: 'extracting', - }, - }, - }, - extracting: { - invoke: { - src: 'extractSummaries', - input: ({ context }) => ({ - searchResult: context.searchResults!, - }), - onDone: { - actions: assign({ - summaries: ({ event }) => event.output, - }), - target: 'generating', - }, - }, - }, - generating: {}, - }, -}); diff --git a/src/stream-snapshot.test.ts b/src/stream-snapshot.test.ts new file mode 100644 index 0000000..22248d5 --- /dev/null +++ b/src/stream-snapshot.test.ts @@ -0,0 +1,77 @@ +import { expect, test } from 'vitest'; +import { createAgentMachine } from './index.js'; + +const machine = createAgentMachine({ + id: 'snapshot-machine', + context: () => ({}), + initial: () => ({ + target: 'done', + input: { step: 1 }, + }), + states: { + done: { + type: 'final', + output: () => ({ ok: true }), + }, + }, +}); + +async function collectSnapshots(state = machine.getInitialState()) { + const snaps = []; + for await (const snap of machine.stream(state)) { + snaps.push(snap); + } + + return snaps; +} + +test('stream emits durable snapshots with stable session metadata', async () => { + const snaps = await collectSnapshots(); + + expect(snaps.length).toBeGreaterThanOrEqual(2); + expect(new Set(snaps.map((snap) => snap.sessionId)).size).toBe(1); + expect(new Set(snaps.map((snap) => snap.createdAt)).size).toBe(1); + expect(snaps[0]!.input).toEqual({ done: { step: 1 } }); + expect(snaps[0]).toEqual( + expect.objectContaining({ + sessionId: expect.any(String), + createdAt: expect.any(Number), + value: 'done', + context: {}, + status: 'active', + input: { done: { step: 1 } }, + }) + ); + expect(snaps[snaps.length - 1]).toEqual( + expect.objectContaining({ + sessionId: snaps[0]!.sessionId, + createdAt: snaps[0]!.createdAt, + value: 'done', + context: {}, + status: 'done', + input: { done: { step: 1 } }, + output: { ok: true }, + }) + ); +}); + +test('snapshot roundtrips through resolveState without losing identity', async () => { + const emitted = await collectSnapshots(); + const restored = machine.resolveState(emitted[0]!); + const rerun = await collectSnapshots(restored); + + expect(restored.sessionId).toBe(emitted[0]!.sessionId); + expect(restored.createdAt).toBe(emitted[0]!.createdAt); + expect(restored.input).toEqual(emitted[0]!.input); + expect(rerun[0]!.sessionId).toBe(emitted[0]!.sessionId); + expect(rerun[0]!.createdAt).toBe(emitted[0]!.createdAt); + expect(rerun[0]!.input).toEqual(emitted[0]!.input); +}); + +test('fresh machine executions on the same raw state get distinct session ids', async () => { + const state = machine.getInitialState(); + const firstRun = await collectSnapshots(state); + const secondRun = await collectSnapshots(state); + + expect(firstRun[0]!.sessionId).not.toBe(secondRun[0]!.sessionId); +}); diff --git a/src/streaming.test.ts b/src/streaming.test.ts new file mode 100644 index 0000000..a2c490a --- /dev/null +++ b/src/streaming.test.ts @@ -0,0 +1,251 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { + createAgentMachine, + createMemoryRunStore, + startSession, +} from './index.js'; + +function once( + subscribe: (handler: (event: T) => void) => () => void +) { + return new Promise((resolve) => { + let off = () => {}; + off = subscribe((event) => { + off(); + resolve(event); + }); + }); +} + +test('returns a live run before initial invoke output and emits ephemeral parts', async () => { + const machine = createAgentMachine({ + id: 'streaming-parts', + schemas: { + emitted: { + textPart: z.object({ delta: z.string() }), + }, + }, + context: () => ({ finalText: '' }), + initial: 'writing', + states: { + writing: { + schemas: { output: z.object({ text: z.string() }) }, + invoke: async (_args, enq) => { + enq.emit({ type: 'textPart', delta: 'hel' }); + enq.emit({ type: 'textPart', delta: 'lo' }); + + return { text: 'hello' }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { finalText: output.text }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ text: context.finalText }), + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + const parts: Array<{ type: string; delta: string }> = []; + const allParts: Array<{ type: string; delta: string }> = []; + const states: string[] = []; + const events: string[] = []; + const done = once(run.onDone.bind(run)); + + const offPart = run.on('textPart', (part) => { + parts.push(part as { type: string; delta: string }); + }); + const offAnyPart = run.onEmitted((part) => { + allParts.push(part); + }); + const offState = run.onSnapshot((snapshot) => { + states.push(snapshot.value); + }); + const offEvent = run.onMachineEvent((event) => { + events.push(event.type); + }); + + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'writing', + status: 'active', + }) + ); + + await done; + + expect(parts).toEqual([ + { type: 'textPart', delta: 'hel' }, + { type: 'textPart', delta: 'lo' }, + ]); + expect(allParts).toEqual([ + { type: 'textPart', delta: 'hel' }, + { type: 'textPart', delta: 'lo' }, + ]); + expect(states.length).toBeGreaterThan(0); + expect(states.every((state) => state === 'done')).toBe(true); + expect(events).toContain('xstate.done.invoke.writing'); + expect(run.getSnapshot().output).toEqual({ text: 'hello' }); + + offPart(); + offAnyPart(); + offState(); + offEvent(); +}); + +test('does not replay prior events to late subscribers', async () => { + const machine = createAgentMachine({ + id: 'late-streaming-subscriber', + schemas: { + emitted: { + textPart: z.object({ delta: z.string() }), + }, + }, + context: () => ({ finalText: '' }), + initial: 'writing', + states: { + writing: { + schemas: { output: z.object({ text: z.string() }) }, + invoke: async (_args, enq) => { + enq.emit({ type: 'textPart', delta: 'hel' }); + enq.emit({ type: 'textPart', delta: 'lo' }); + return { text: 'hello' }; + }, + onDone: ({ output }) => ({ + target: 'done', + context: { finalText: output.text }, + }), + }, + done: { + type: 'final', + output: ({ context }) => ({ text: context.finalText }), + }, + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + await once(run.onDone.bind(run)); + + const lateParts: Array<{ type: string; delta: string }> = []; + const replayedStates: string[] = []; + const replayedEvents: string[] = []; + + run.on('textPart', (part) => { + lateParts.push(part); + }); + run.onSnapshot((snapshot) => { + replayedStates.push(snapshot.value); + }); + run.onMachineEvent((event) => { + replayedEvents.push(event.type); + }); + run.onDone(() => { + replayedEvents.push('done'); + }); + + expect(lateParts).toEqual([]); + expect(replayedStates).toEqual([]); + expect(replayedEvents).toEqual([]); +}); + +test('invalid emitted parts are rejected', async () => { + const machine = createAgentMachine({ + id: 'streaming-invalid-parts', + schemas: { + emitted: { + textPart: z.object({ delta: z.string().min(1) }), + }, + }, + context: () => ({ count: 0 }), + initial: 'writing', + states: { + writing: { + invoke: async (_args, enq) => { + enq.emit({ type: 'textPart', delta: '' }); + return { ok: true }; + }, + }, + }, + }); + + const run = await startSession(machine, { + store: createMemoryRunStore(), + }); + await once(run.onError.bind(run)); + + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'writing', + status: 'error', + error: expect.objectContaining({ + message: expect.stringContaining("Invalid emitted part 'textPart'"), + }), + }) + ); +}); + +test('transition handlers can emit live effects without journaling them', async () => { + const machine = createAgentMachine({ + id: 'transition-handler-emits', + schemas: { + emitted: { + textPart: z.object({ delta: z.string() }), + }, + events: { + send: z.object({}), + }, + }, + context: () => ({ sent: false }), + initial: 'ready', + states: { + ready: { + on: { + send: ({ context }, enq) => { + enq.emit({ type: 'textPart', delta: 'sending' }); + + return { + target: 'done', + context: { sent: !context.sent }, + }; + }, + }, + }, + done: { + type: 'final', + output: ({ context }) => context, + }, + }, + }); + + const store = createMemoryRunStore(); + const run = await startSession(machine, { store }); + const parts: string[] = []; + + run.on('textPart', (part) => { + parts.push(part.delta); + }); + + await run.send({ type: 'send' }); + + expect(parts).toEqual(['sending']); + expect(run.getSnapshot()).toEqual( + expect.objectContaining({ + value: 'done', + status: 'done', + context: { sent: true }, + }) + ); + + const journal = await store.loadEvents(run.sessionId); + expect(journal.map((event) => event.type)).toEqual([ + 'xstate.init', + 'send', + ]); +}); diff --git a/src/target-types.assert.ts b/src/target-types.assert.ts new file mode 100644 index 0000000..6cd6cae --- /dev/null +++ b/src/target-types.assert.ts @@ -0,0 +1,279 @@ +import { z } from 'zod'; +import { createAgentMachine } from './machine.js'; + +const machine = createAgentMachine({ + id: 'typed-targets', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + advance: () => ({ + target: 'done', + }), + }, + }, + done: { + type: 'final', + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +machine.transition(machine.getInitialState(), { type: 'advance' }); + +createAgentMachine({ + id: 'typed-target-input', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + advance: () => ({ + target: 'working', + input: { + index: 0, + }, + }), + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }) }, + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +const typedMachine = createAgentMachine({ + id: 'typed-surface', + schemas: { + input: z.object({ + task: z.string(), + }), + events: { + submit: z.object({ + value: z.number(), + }), + }, + output: z.object({ + task: z.string(), + total: z.number(), + }), + }, + context: (input) => ({ + task: input.task, + total: 0, + }), + initial: 'idle', + states: { + idle: { + on: { + submit: ({ event }) => { + event.value satisfies number; + // @ts-expect-error invalid event payload property + event.missing; + return { + target: 'done', + context: { total: event.value }, + }; + }, + }, + }, + done: { + type: 'final', + output: ({ context }) => ({ + task: context.task, + total: context.total, + }), + }, + }, +}); + +typedMachine.getInitialState({ task: 'ship it' }); +// @ts-expect-error missing required input +typedMachine.getInitialState(); +// @ts-expect-error wrong input type +typedMachine.getInitialState({ task: 42 }); + +const typedState = typedMachine.getInitialState({ task: 'infer state values' }); +typedState.value satisfies 'idle' | 'done'; +// @ts-expect-error invalid state literal +typedState.value satisfies 'missing'; + +typedMachine.transition(typedState, { type: 'submit', value: 1 }); +// @ts-expect-error invalid event type +typedMachine.transition(typedState, { type: 'missing' }); +// @ts-expect-error invalid event payload +typedMachine.transition(typedState, { type: 'submit', value: 'nope' }); + +void (async () => { + const result = await typedMachine.execute( + typedMachine.transition(typedState, { type: 'submit', value: 2 }) + ); + + if (result.status === 'done') { + result.output.total satisfies number; + result.output.task satisfies string; + // @ts-expect-error no missing output property + result.output.missing; + } +})(); + +createAgentMachine({ + id: 'missing-required-target-input', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error input should be required when the target has schemas.input + advance: () => ({ + target: 'working', + }), + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }) }, + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +createAgentMachine({ + id: 'invalid-target', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error invalid targets should be rejected at author time + advance: () => ({ + target: 'missing', + }), + }, + }, + done: { + type: 'final', + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +createAgentMachine({ + id: 'unexpected-target-input', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error input should be rejected when the target has no schemas.input + advance: () => ({ + target: 'done', + input: { + anything: true, + }, + }), + }, + }, + done: { + type: 'final', + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +createAgentMachine({ + id: 'invalid-target-input', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error target input should match the target state's input schema + advance: () => ({ + target: 'working', + input: { + wrong: true, + }, + }), + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }) }, + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); + +createAgentMachine({ + id: 'invalid-target-param-types', + context: () => ({ count: 0 }), + initial: 'idle', + states: { + idle: { + on: { + // @ts-expect-error target input should match the target input field types + advance: () => ({ + target: 'working', + input: { + index: 'hello', + }, + }), + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }) }, + }, + }, + schemas: { + events: { + advance: z.object({ + type: z.literal('advance'), + }), + }, + }, +}); diff --git a/src/templates/defaultText.ts b/src/templates/defaultText.ts deleted file mode 100644 index 2cb841b..0000000 --- a/src/templates/defaultText.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { PromptTemplate } from '../types'; -import { wrapInXml } from '../utils'; - -export const defaultTextTemplate: PromptTemplate = (data) => { - const preamble = [ - data.context - ? wrapInXml('context', JSON.stringify(data.context)) - : undefined, - ] - .filter(Boolean) - .join('\n'); - - return ` -${preamble} - -${data.goal} - `.trim(); -}; diff --git a/src/text.ts b/src/text.ts deleted file mode 100644 index 5f66b6e..0000000 --- a/src/text.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - generateText, - streamText, - type CoreMessage, - type CoreTool, - type GenerateTextResult, -} from 'ai'; -import { - AgentGenerateTextOptions, - AgentStreamTextOptions, - AnyAgent, -} from './types'; -import { defaultTextTemplate } from './templates/defaultText'; -import { - ObservableActorLogic, - Observer, - PromiseActorLogic, - fromObservable, - fromPromise, - toObserver, -} from 'xstate'; - -/** - * Gets an array of messages from the given prompt, based on the agent and options. - * - * @param agent - * @param prompt - * @param options - * @returns - */ -export async function getMessages( - agent: AnyAgent, - prompt: string, - options: Omit -): Promise { - let messages: CoreMessage[] = []; - if (typeof options.messages === 'function') { - messages = await options.messages(agent); - } else if (options.messages) { - messages = options.messages; - } - - messages = messages.concat({ - role: 'user', - content: prompt, - }); - - return messages; -} - -export function fromTextStream( - agent: T, - options?: AgentStreamTextOptions -): ObservableActorLogic< - { textDelta: string }, - Omit & { - context?: AgentStreamTextOptions['context']; - } -> { - const template = options?.template ?? defaultTextTemplate; - return fromObservable(({ input }) => { - const observers = new Set>(); - - // TODO: check if messages was provided instead - - (async () => { - const model = input.model ? agent.wrap(input.model) : agent.model; - const goal = - typeof input.prompt === 'string' - ? input.prompt - : await input.prompt(agent); - const promptWithContext = template({ - goal, - context: input.context, - }); - const messages = await getMessages(agent, promptWithContext, input); - const result = await streamText({ - ...options, - ...input, - prompt: undefined, // overwritten by messages - model, - messages, - }); - - for await (const part of result.fullStream) { - if (part.type === 'text-delta') { - observers.forEach((observer) => { - observer.next?.(part); - }); - } - } - })(); - - return { - subscribe: (...args: any[]) => { - const observer = toObserver(...args); - observers.add(observer); - - return { - unsubscribe: () => { - observers.delete(observer); - }, - }; - }, - }; - }); -} - -export function fromText( - agent: T, - options?: AgentGenerateTextOptions -): PromiseActorLogic< - GenerateTextResult>>, - Omit & { - context?: AgentGenerateTextOptions['context']; - } -> { - const resolvedOptions = { - ...agent.defaultOptions, - ...options, - }; - - const template = resolvedOptions.template ?? defaultTextTemplate; - - return fromPromise(async ({ input }) => { - const goal = - typeof input.prompt === 'string' - ? input.prompt - : await input.prompt(agent); - - const promptWithContext = template({ - goal, - context: input.context, - }); - - const messages = await getMessages(agent, promptWithContext, input); - - const model = input.model ? agent.wrap(input.model) : agent.model; - - return await generateText({ - ...input, - ...options, - prompt: undefined, - messages, - model, - }); - }); -} diff --git a/src/types.ts b/src/types.ts index 5e504ca..7c88a04 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,447 +1,426 @@ -import { - ActorLogic, - ActorRefFrom, - AnyActorRef, - AnyEventObject, - AnyStateMachine, - EventFrom, - EventObject, - PromiseActorLogic, - SnapshotFrom, - StateValue, - Subscription, - TransitionSnapshot, - Values, -} from 'xstate'; -import { - CoreMessage, - generateText, - GenerateTextResult, - LanguageModel, - streamText, -} from 'ai'; -import { ZodContextMapping, ZodEventMapping } from './schemas'; -import { TypeOf } from 'zod'; -import { Agent } from './agent'; - -export type GenerateTextOptions = Parameters[0]; - -export type StreamTextOptions = Parameters[0]; - -export type AgentPlanInput = Omit< - GenerateTextOptions, - 'prompt' | 'tools' -> & { - /** - * The currently observed state. - */ - state: ObservedState; - /** - * The goal for the agent to accomplish. - * The agent will create a plan based on this goal. - */ - goal: string; - /** - * The events that the agent can trigger. This is a mapping of - * event types to Zod event schemas. - */ - events: ZodEventMapping; - /** - * The state machine that represents the environment the agent - * is interacting with. - */ - machine?: AnyStateMachine; - /** - * The previous plan. - */ - previousPlan?: AgentPlan; -}; +// ─── Standard Schema V1 ─── + +export interface StandardSchemaV1 { + readonly '~standard': { + readonly version: 1; + readonly vendor: string; + readonly validate: (value: unknown) => any; + readonly types?: { readonly input?: unknown; readonly output?: Output }; + }; +} -export type AgentPlan = { - goal: string; - state: ObservedState; - content?: string; - /** - * Executes the plan based on the given `state` and resolves with - * a potential next `event` to trigger to achieve the `goal`. - */ - execute: (state: ObservedState) => Promise; - nextEvent: TEvent | undefined; - sessionId: string; - timestamp: number; -}; +export type StandardSchemaResult = + | { value: T; issues?: undefined } + | { value?: undefined; issues: ReadonlyArray<{ message: string }> }; -export interface TransitionData { - eventType: string; - description?: string; - guard?: { type: string }; - target?: any; -} +export type InferOutput = T extends StandardSchemaV1 ? O : never; -export type PromptTemplate = (data: { - goal: string; - /** - * The observed state - */ - state?: ObservedState; - /** - * The context to provide. - * This overrides the observed state.context, if provided. - */ - context?: any; - /** - * The state machine model of the observed environment - */ - machine?: unknown; - /** - * The potential next transitions that can be taken - * in the state machine - */ - transitions?: TransitionData[]; - /** - * Past observations - */ - observations?: AgentObservation[]; // TODO - feedback?: AgentFeedback[]; - messages?: AgentMessage[]; - plans?: AgentPlan[]; -}) => string; - -export type AgentPlanner = ( - agent: T, - input: AgentPlanInput -) => Promise | undefined>; - -export type AgentDecideOptions = { - goal: string; - model?: LanguageModel; - state: ObservedState; - machine?: AnyStateMachine; - execute?: (event: AnyEventObject) => Promise; - planner?: AgentPlanner; - events?: ZodEventMapping; -} & Omit[0], 'model' | 'tools' | 'prompt'>; - -export interface AgentFeedback { - goal?: string; - observationId?: string; - /** - * The message correlation that the feedback is relevant for - */ - correlationId?: string; - attributes: Record; - reward: number; - timestamp: number; - sessionId: string; -} +// ─── Event Helpers ─── -export interface AgentFeedbackInput { - goal?: string; - observationId?: string; - correlationId?: string; - attributes?: Record; - timestamp?: number; - reward?: number; -} +export type EventPayload = T extends Record ? unknown : T; -export type AgentMessage = CoreMessage & { - timestamp: number; - id: string; - /** - * The response ID of the message, which references - * which message this message is responding to, if any. - */ - responseId?: string; - result?: GenerateTextResult; - sessionId: string; -}; +export type EventUnion> = { + [K in keyof T & string]: { type: K } & EventPayload>; +}[keyof T & string]; + +export type EmittedUnion> = EventUnion; + +export type TransitionEvent< + TEvents extends Record, +> = [keyof TEvents & string] extends [never] + ? { type: string; [key: string]: unknown } + : EventUnion; + +export type EmittedPart = { type: string; [key: string]: unknown }; -type JSONObject = { - [key: string]: JSONValue; +export type AgentMessage = { + role: string; + content: string; + [key: string]: unknown; }; -type JSONArray = JSONValue[]; -type JSONValue = null | string | number | boolean | JSONObject | JSONArray; -type LanguageModelV1ProviderMetadata = Record< - string, - Record +export type AgentTools = Record; + +export type AgentToolChoice = + | string + | number + | boolean + | null + | readonly unknown[] + | { [key: string]: unknown }; + +export type AgentResolverSnapshot< + TContext extends Record = Record, +> = Omit< + AgentState, + 'model' | 'prompt' | 'system' | 'tools' | 'toolChoice' >; -interface LanguageModelV1ImagePart { - type: 'image'; - /** -Image data as a Uint8Array (e.g. from a Blob or Buffer) or a URL. - */ - image: Uint8Array | URL; - /** -Optional mime type of the image. - */ - mimeType?: string; - /** - * Additional provider-specific metadata. They are passed through - * to the provider from the AI SDK and enable provider-specific - * functionality that can be fully encapsulated in the provider. - */ - providerMetadata?: LanguageModelV1ProviderMetadata; -} +export type StateResolverArgs< + TContext extends Record, + TInput = Record, +> = { + snapshot: AgentResolverSnapshot; + context: TContext; + messages: AgentMessage[]; + input: TInput; +}; -export interface LanguageModelV1TextPart { - type: 'text'; - /** -The text content. - */ - text: string; - /** - * Additional provider-specific metadata. They are passed through - * to the provider from the AI SDK and enable provider-specific - * functionality that can be fully encapsulated in the provider. - */ - providerMetadata?: LanguageModelV1ProviderMetadata; -} +export type ResolvableStateValue< + TValue, + TContext extends Record, + TInput = Record, +> = + | TValue + | ((args: StateResolverArgs) => TValue); -export interface LanguageModelV1ToolCallPart { - type: 'tool-call'; - /** -ID of the tool call. This ID is used to match the tool call with the tool result. - */ - toolCallId: string; - /** -Name of the tool that is being called. - */ - toolName: string; - /** -Arguments of the tool call. This is a JSON-serializable object that matches the tool's input schema. - */ - args: unknown; - /** - * Additional provider-specific metadata. They are passed through - * to the provider from the AI SDK and enable provider-specific - * functionality that can be fully encapsulated in the provider. - */ - providerMetadata?: LanguageModelV1ProviderMetadata; +export interface InvokeEnqueue { + emit(part: EmittedPart): void; } -interface LanguageModelV1ToolResultPart { - type: 'tool-result'; - /** -ID of the tool call that this result is associated with. - */ - toolCallId: string; - /** -Name of the tool that generated this result. - */ - toolName: string; - /** -Result of the tool call. This is a JSON-serializable object. - */ - result: unknown; - /** -Optional flag if the result is an error or an error message. - */ - isError?: boolean; - /** - * Additional provider-specific metadata. They are passed through - * to the provider from the AI SDK and enable provider-specific - * functionality that can be fully encapsulated in the provider. - */ - providerMetadata?: LanguageModelV1ProviderMetadata; -} -type LanguageModelV1Message = ( - | { - role: 'system'; - content: string; - } - | { - role: 'user'; - content: Array; - } - | { - role: 'assistant'; - content: Array; - } - | { - role: 'tool'; - content: Array; - } -) & { - /** - * Additional provider-specific metadata. They are passed through - * to the provider from the AI SDK and enable provider-specific - * functionality that can be fully encapsulated in the provider. - */ - providerMetadata?: LanguageModelV1ProviderMetadata; -}; -export type AgentMessageInput = CoreMessage & { - timestamp?: number; - id?: string; - /** - * The response ID of the message, which references - * which message this message is responding to, if any. - */ - responseId?: string; - correlationId?: string; - parentCorrelationId?: string; - result?: GenerateTextResult; -}; +type IsExactlyUnknown = unknown extends T + ? ([T] extends [unknown] ? true : false) + : false; -export interface AgentObservation { - id: string; - prevState: SnapshotFrom | undefined; - event: EventFrom; - state: SnapshotFrom; - machineHash: string | undefined; - sessionId: string; - timestamp: number; -} +// ─── Durable Session Vocabulary ─── + +export type { JournalEvent } from './runtime/events.js'; +export type { JournalEventRecord, PersistedSnapshot, RunStore } from './runtime/store.js'; -export interface AgentObservationInput { - id?: string; - prevState: ObservedState | undefined; - event: AnyEventObject; - state: ObservedState; - machine?: AnyStateMachine; - timestamp?: number; +// ─── Adapter ─── + +export interface AgentAdapter { + generateText?: (options: { + model?: string; + system?: string; + prompt?: string; + messages: AgentMessage[]; + tools?: AgentTools; + toolChoice?: unknown; + outputSchema?: StandardSchemaV1; + }) => Promise; } -export type AgentDecisionInput = { - goal: string; - model?: LanguageModel; - context?: any; -} & Omit[0], 'model' | 'tools' | 'prompt'>; +export interface DecideAdapter { + decide: (options: { + model: string; + prompt: string; + options: Record; + reasoning?: boolean; + }) => Promise<{ + choice: string; + data: Record; + reasoning?: string; + }>; +} -export type AgentDecisionLogic = PromiseActorLogic< - AgentPlan | undefined, - AgentDecisionInput | string ->; +// ─── Transition ─── -export type AgentEmitted = +export type TransitionResult< + TContext extends Record = Record, + TTarget extends string = string, + TInputByTarget extends Record = {}, +> = | { - type: 'feedback'; - feedback: AgentFeedback; + target?: undefined; + context?: Partial; + messages?: AgentMessage[]; + input?: never; } | { - type: 'observation'; - observation: AgentObservation; // TODO - } - | { - type: 'message'; - message: AgentMessage; - } - | { - type: 'plan'; - plan: AgentPlan; - }; + [K in TTarget]: { + target: K; + context?: Partial; + messages?: AgentMessage[]; + } & (K extends keyof TInputByTarget + ? IsExactlyUnknown extends true + ? { input?: never } + : { input: TInputByTarget[K] } + : { input?: never }) + }[TTarget]; + +export interface InitialTransitionResult< + TContext extends Record = Record, + TTarget extends string = string, +> { + target: TTarget; + context?: Partial; + messages?: AgentMessage[]; + input?: Record; +} -export type AgentLogic = ActorLogic< - TransitionSnapshot, - | { - type: 'agent.feedback'; - feedback: AgentFeedback; - } - | { - type: 'agent.observe'; - observation: AgentObservation; // TODO - } - | { - type: 'agent.message'; - message: AgentMessage; - } - | { - type: 'agent.plan'; - plan: AgentPlan; - }, - any, // TODO: input - any, - AgentEmitted ->; +// ─── State Config ─── + +export interface StateConfig< + TContext extends Record = Record, + TTarget extends string = string, + TInputByTarget extends Record = {}, +> { + type?: 'final'; + schemas?: { + input?: StandardSchemaV1; + output?: StandardSchemaV1; + }; + invoke?: (args: { + context: TContext; + messages: AgentMessage[]; + input: Record; + signal?: AbortSignal; + }, enq: InvokeEnqueue) => Promise; + onDone?: (args: { output: any; context: TContext; messages: AgentMessage[] }) => TransitionResult; + always?: TransitionResult | ((args: { context: TContext; messages: AgentMessage[]; input: Record }, enq: InvokeEnqueue) => TransitionResult); + on?: Record | ((args: { event: any; context: TContext; messages: AgentMessage[] }, enq: InvokeEnqueue) => TransitionResult)>; + events?: Record; + output?: (args: { context: TContext; messages: AgentMessage[] }) => unknown; + model?: ResolvableStateValue; + adapter?: AgentAdapter; + prompt?: ResolvableStateValue; + system?: ResolvableStateValue; + tools?: ResolvableStateValue; + toolChoice?: ResolvableStateValue; +} -export type EventsFromZodEventMapping = - Values<{ - [K in keyof TEventSchemas & string]: { - type: K; - } & TypeOf; - }>; +type OutputForState = TState extends { + output: (...args: any[]) => infer TOutput; +} + ? TOutput + : never; + +export type OutputForStates> = + [OutputForState] extends [never] + ? unknown + : OutputForState; + +// ─── Agent State (POJO) ─── + +export interface AgentState< + TContext extends Record = Record, + TValue extends string = string, + TOutput = unknown, +> { + value: TValue; + context: TContext; + messages: AgentMessage[]; + status: 'active' | 'pending' | 'done' | 'error'; + input: Record>; + sessionId?: string; + createdAt?: number; + output?: TOutput; + error?: unknown; + model?: string; + prompt?: string; + system?: string; + tools?: AgentTools; + toolChoice?: unknown; +} -export type ContextFromZodContextMapping< - TContextSchema extends ZodContextMapping -> = { - [K in keyof TContextSchema & string]: TypeOf; -}; +// ─── Execute Result ─── + +export type ExecuteResult< + TContext extends Record = Record, + TValue extends string = string, + TEvents extends Record = {}, + TOutput = unknown, +> = + | { status: 'done'; state: AgentState; output: TOutput; context: TContext; messages: AgentMessage[] } + | { status: 'pending'; state: AgentState; value: TValue; events: Record; context: TContext; messages: AgentMessage[] } + | { status: 'error'; state: AgentState; error: unknown }; + +// ─── Snapshot ─── + +export interface AgentSnapshot< + TContext extends Record = Record, + TValue extends string = string, + TOutput = unknown, +> { + value: TValue; + context: TContext; + messages: AgentMessage[]; + status: AgentState['status']; + createdAt: number; + sessionId: string; + input: Record>; + output?: TOutput; + error?: unknown; +} -export type AnyAgent = Agent; +// ─── Agent Machine ─── + +export interface AgentMachine< + TInput = unknown, + TContext extends Record = Record, + TEvents extends Record = {}, + TStates extends Record = Record>, + TOutput = OutputForStates, + TEmitted extends Record = {}, +> { + readonly id: string; + /** @internal */ + readonly __config?: unknown; + + getInitialState( + ...args: unknown extends TInput ? [input?: TInput] : [input: TInput] + ): AgentState; + + resolveState( + raw: + | AgentSnapshot + | { + value: string; + context: TContext; + messages?: AgentMessage[]; + input?: Record>; + sessionId?: string; + createdAt?: number; + status?: AgentState['status']; + output?: TOutput; + error?: unknown; + } + ): AgentState; + + transition( + state: AgentState, + event: TransitionEvent + ): AgentState; + + invoke( + state: AgentState + ): Promise>; + + execute( + state: AgentState + ): Promise>; + + stream( + state: AgentState + ): AsyncGenerator>; +} -export type FromAgent = T | ((agent: AnyAgent) => T | Promise); +export interface AgentRun< + TContext extends Record = Record, + TValue extends string = string, + TEvents extends Record = {}, + TOutput = unknown, + TEmitted extends Record = {}, +> { + readonly sessionId: string; + readonly status: AgentSnapshot['status']; + getSnapshot(): AgentSnapshot; + send(event: TransitionEvent): Promise; + on( + type: TKey, + handler: (event: { type: TKey } & EventPayload>) => void + ): () => void; + onEmitted( + handler: (event: EmittedUnion) => void + ): () => void; + onDone( + handler: (event: { + output: TOutput; + snapshot: AgentSnapshot; + }) => void + ): () => void; + onError( + handler: (event: { + error: unknown; + snapshot: AgentSnapshot; + }) => void + ): () => void; + onSnapshot( + handler: (snapshot: AgentSnapshot) => void + ): () => void; + onMachineEvent( + handler: ( + event: import('./runtime/store.js').JournalEventRecord< + import('./runtime/events.js').JournalEvent + > + ) => void + ): () => void; +} -export type CommonTextOptions = { - prompt: FromAgent; - model?: LanguageModel; - context?: Record; - messages?: FromAgent; - template?: PromptTemplate; -}; +export interface SessionOptions< + TInput = unknown, + TSnapshot extends AgentSnapshot = AgentSnapshot, +> { + input?: TInput; + sessionId?: string; + store: import('./runtime/store.js').RunStore; +} -export type AgentGenerateTextOptions = Omit< - GenerateTextOptions, - 'model' | 'prompt' | 'messages' -> & - CommonTextOptions; - -export type AgentStreamTextOptions = Omit< - StreamTextOptions, - 'model' | 'prompt' | 'messages' -> & - CommonTextOptions; - -export interface ObservedState { - /** - * The current state value of the state machine, e.g. - * `"loading"` or `"processing"` or `"ready"` - */ - value: StateValue; - /** - * Additional contextual data related to the current state - */ - context: Record; +export interface RestoreSessionOptions< + TSnapshot extends AgentSnapshot = AgentSnapshot, +> { + sessionId: string; + store: import('./runtime/store.js').RunStore; } -export type ObservedStateFrom = Pick< - SnapshotFrom, - 'value' | 'context' ->; +// ─── Machine Config (internal) ─── -export type AgentMemoryContext = { - observations: AgentObservation[]; // TODO - messages: AgentMessage[]; - plans: AgentPlan[]; - feedback: AgentFeedback[]; -}; +export interface MachineConfig< + TInput = unknown, + TContext extends Record = Record, + TEvents extends Record = {}, + TStates extends Record = Record>, + TEmitted extends Record = {}, +> { + id: string; + schemas?: { + input?: StandardSchemaV1; + context?: StandardSchemaV1; + events?: TEvents; + emitted?: TEmitted; + output?: StandardSchemaV1; + }; + context: (input: TInput) => TContext; + messages?: AgentMessage[] | ((input: TInput) => AgentMessage[]); + adapter?: AgentAdapter; + externalEvents?: readonly string[]; + initial: + | (keyof TStates & string) + | ((args: { context: TContext }) => { target: keyof TStates & string; input?: Record }); + states: TStates; +} -export type AgentMemory = AppendOnlyStorage; - -export interface AppendOnlyStorage> { - append( - sessionId: string, - key: K, - item: T[K][0] - ): Promise; - getAll( - sessionId: string, - key: K - ): Promise; +export type DecideResultFor< + TOptions extends Record, +> = { + [K in keyof TOptions & string]: { + choice: K; + data: TOptions[K] extends { schema: StandardSchemaV1 } ? O : Record; + reasoning?: string; + }; +}[keyof TOptions & string]; + +export interface DecideOptions< + TOptions extends Record = Record, +> { + adapter?: DecideAdapter; + model: string; + prompt: string; + options: TOptions; + reasoning?: boolean; } -export interface AgentLongTermMemory { - get( - key: K - ): Promise; - append( - key: K, - item: AgentMemoryContext[K][0] - ): Promise; - set( - key: K, - items: AgentMemoryContext[K] - ): Promise; +export interface ClassifyResultFor< + TCategories extends Record = Record, +> { + category: keyof TCategories & string; } -export type Compute = { [K in keyof A]: A[K] } & unknown; +export interface ClassifyOptions< + TCategories extends Record = Record, +> { + adapter?: DecideAdapter; + model: string; + prompt: string; + into: TCategories; + examples?: Array<{ input: string; category: keyof TCategories & string }>; + reasoning?: boolean; +} + +// ─── Trace ─── + +export interface Trace { + state: string; + event: { type: string; timestamp: number; [key: string]: unknown }; +} diff --git a/src/utils.ts b/src/utils.ts index a1ae4ac..6984215 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,72 +1,291 @@ -import { AnyMachineSnapshot, AnyStateMachine, AnyStateNode } from 'xstate'; -import hash from 'object-hash'; -import { TransitionData } from './types'; - -export function getAllTransitions(state: AnyMachineSnapshot): TransitionData[] { - const nodes = state._nodes; - const transitions = (nodes as AnyStateNode[]) - .map((node) => [...(node as AnyStateNode).transitions.values()]) - .map((nodeTransitions) => { - return nodeTransitions.map((nodeEventTransitions) => { - return nodeEventTransitions.map((transition) => { - return { - ...transition, - guard: - typeof transition.guard === 'string' - ? { type: transition.guard } - : (transition.guard as any), // TODO: fix - }; - }); - }); - }) - .flat(2); - - return transitions; -} - -export function getAllMachineTransitions( - stateNode: AnyStateNode -): TransitionData[] { - const transitions: TransitionData[] = [...stateNode.transitions.values()] - .map((nodeTransitions) => { - return nodeTransitions.map((transition) => { - return { - ...transition, - guard: - typeof transition.guard === 'string' - ? { type: transition.guard } - : (transition.guard as any), // TODO: fix - }; - }); - }) - .flat(2); - - for (const s of Object.values(stateNode.states)) { - const stateTransitions = getAllMachineTransitions(s); - transitions.push(...stateTransitions); +import type { + AgentMessage, + AgentState, + AgentToolChoice, + InitialTransitionResult, + MachineConfig, + StandardSchemaResult, + StandardSchemaV1, + TransitionResult, +} from './types.js'; + +/** + * Validate a value against a Standard Schema synchronously. + */ +export function validateSchemaSync( + schema: StandardSchemaV1, + value: unknown +): T { + const result = schema['~standard'].validate(value); + if (result instanceof Promise) { + throw new Error( + 'Async schema validation is not supported in sync context.' + ); + } + const syncResult = result as StandardSchemaResult; + if (syncResult.issues) { + const messages = syncResult.issues + .map((i: { message: string }) => i.message) + .join(', '); + throw new Error(`Validation failed: ${messages}`); + } + return syncResult.value as T; +} + +/** + * Get the state config for a given state name. + */ +export function resolveStateConfig( + config: MachineConfig, + value: string +): StateConfigAny { + const stateConfig = config.states[value]; + if (!stateConfig) { + throw new Error(`State '${value}' not found`); } + return stateConfig as StateConfigAny; +} + +/** Loose state config for internal runtime use */ +export type StateConfigAny = { + type?: 'final'; + invoke?: ( + args: { + context: Record; + messages: AgentMessage[]; + input: Record; + }, + enq: { emit(part: { type: string; [key: string]: unknown }): void } + ) => Promise; + onDone?: (args: { output: unknown; context: Record; messages: AgentMessage[] }) => TransitionResult; + always?: TransitionResult | ((args: { context: Record; messages: AgentMessage[]; input: Record }, enq: { emit(part: { type: string; [key: string]: unknown }): void }) => TransitionResult); + on?: Record; context: Record; messages: AgentMessage[] }, enq: { emit(part: { type: string; [key: string]: unknown }): void }) => TransitionResult)>; + output?: (args: { context: Record; messages: AgentMessage[] }) => unknown; + schemas?: { + input?: StandardSchemaV1; + output?: StandardSchemaV1; + }; + model?: string | ((args: { + snapshot: AgentState; + context: Record; + messages: AgentMessage[]; + input: Record; + }) => string); + adapter?: { + generateText?: (...args: unknown[]) => Promise; + }; + prompt?: string | ((args: { + snapshot: AgentState; + context: Record; + messages: AgentMessage[]; + input: Record; + }) => string); + system?: string | ((args: { + snapshot: AgentState; + context: Record; + messages: AgentMessage[]; + input: Record; + }) => string); + tools?: Record | ((args: { + snapshot: AgentState; + context: Record; + messages: AgentMessage[]; + input: Record; + }) => Record); + toolChoice?: AgentToolChoice | ((args: { + snapshot: AgentState; + context: Record; + messages: AgentMessage[]; + input: Record; + }) => unknown); + events?: Record; +}; + +/** + * Get the input for the current state. + */ +export function getInput( + value: string, + input: Record> +): Record { + return input[value] ?? {}; +} - return transitions; +/** + * Resolve an initial transition (string shorthand or function). + */ +export function resolveInitial( + initial: + | string + | ((args: { + context: Record; + input: Record; + }) => InitialTransitionResult), + args: { + context: Record; + input: Record; + } +): InitialTransitionResult { + if (typeof initial === 'string') { + return { target: initial }; + } + return initial(args); } -export function wrapInXml(tagName: string, content: string): string { - return `<${tagName}>${content}`; +/** + * Apply a transition result to produce a new state. + */ +export function applyTransition( + state: AgentState, + transition: TransitionResult +): AgentState { + let newState = { ...state }; + + if (transition.context) { + newState.context = { ...state.context, ...transition.context }; + } + + if (transition.messages) { + newState.messages = transition.messages; + } + + if (transition.target) { + newState.value = transition.target; + newState.status = 'active'; + + if (transition.input) { + newState.input = { + ...state.input, + [transition.target]: transition.input, + }; + } + } + + return newState; } -export function randomId() { - const timestamp = Date.now().toString(36); - const random = Math.random().toString(36).substring(2, 9); - return timestamp + random; +/** + * Collect available events for a state. + */ +export function getAvailableEvents( + config: MachineConfig, + value: string +): Record { + const events: Record = {}; + + if (config.schemas?.events) { + Object.assign(events, config.schemas.events); + } + + const stateConfig = resolveStateConfig(config, value); + if (stateConfig.events) { + Object.assign(events, stateConfig.events); + } + + if (stateConfig.on) { + const handled = new Set(Object.keys(stateConfig.on)); + const result: Record = {}; + for (const key of handled) { + if (events[key]) { + result[key] = events[key]; + } + } + return result; + } + + return {}; } -const machineHashes: WeakMap = new WeakMap(); /** - * Returns a string hash representing only the transitions in the state machine. + * Find the event schema for a given event type. */ -export function getMachineHash(machine: AnyStateMachine): string { - if (machineHashes.has(machine)) return machineHashes.get(machine)!; - const transitions = getAllMachineTransitions(machine.root); - const machineHash = hash(transitions); - machineHashes.set(machine, machineHash); - return machineHash; +export function findEventSchema( + config: MachineConfig, + value: string, + eventType: string +): StandardSchemaV1 | undefined { + const stateConfig = resolveStateConfig(config, value); + if (stateConfig.events?.[eventType]) { + return stateConfig.events[eventType]; + } + const events = config.schemas?.events as Record | undefined; + return events?.[eventType]; +} + +export function findEmittedSchema( + config: MachineConfig, + eventType: string +): StandardSchemaV1 | undefined { + const emitted = config.schemas?.emitted as + | Record + | undefined; + + return emitted?.[eventType]; +} + +export function formatSchemaIssues( + issues: ReadonlyArray<{ message: string }> +): string { + return issues.map((issue) => issue.message).join(', '); +} + +export function isDoneInvokeEventType( + stateValue: string, + eventType: string +): boolean { + return eventType === `xstate.done.invoke.${stateValue}`; +} + +export function isErrorInvokeEventType( + stateValue: string, + eventType: string +): boolean { + return eventType === `xstate.error.invoke.${stateValue}`; +} + +export function isAlwaysEventType( + stateValue: string, + eventType: string +): boolean { + return eventType === `xstate.always.${stateValue}`; +} + +export function isReservedInternalEventType(eventType: string): boolean { + return ( + eventType === 'xstate.init' + || eventType.startsWith('xstate.done.invoke.') + || eventType.startsWith('xstate.error.invoke.') + || eventType.startsWith('xstate.always.') + ); +} + +export function serializeError(error: unknown): unknown { + if (error instanceof Error) { + return { + name: error.name, + message: error.message, + stack: error.stack, + }; + } + + return error; +} + +export function appendMessages( + messages: readonly AgentMessage[], + ...nextMessages: AgentMessage[] +): AgentMessage[] { + return messages.concat(nextMessages); +} + +export function userMessage(content: string, extras: Record = {}): AgentMessage { + return { role: 'user', content, ...extras }; +} + +export function assistantMessage(content: string, extras: Record = {}): AgentMessage { + return { role: 'assistant', content, ...extras }; +} + +export function systemMessage(content: string, extras: Record = {}): AgentMessage { + return { role: 'system', content, ...extras }; } diff --git a/src/xstate/index.test.ts b/src/xstate/index.test.ts new file mode 100644 index 0000000..ee15627 --- /dev/null +++ b/src/xstate/index.test.ts @@ -0,0 +1,151 @@ +import { expect, test } from 'vitest'; +import { z } from 'zod'; +import { createAgentMachine } from '../index.js'; +import { toXStateMachine, toXStateVisualization } from './index.js'; + +test('exports a serializable XState config for visualization', () => { + const machine = createAgentMachine({ + id: 'xstate-export', + schemas: { + events: { + submit: z.object({ + type: z.literal('submit'), + count: z.number(), + }), + }, + }, + context: () => ({ total: 0 }), + initial: 'idle', + states: { + idle: { + on: { + submit: ({ event }) => { + if (event.count > 0) { + return { + target: 'working', + context: { total: event.count }, + input: { index: event.count }, + }; + } + + return { target: 'done' }; + }, + }, + }, + working: { + schemas: { input: z.object({ + index: z.number(), + }), output: z.object({ + ok: z.boolean(), + }) }, + invoke: async () => ({ ok: true }), + onDone: () => ({ + target: 'done', + }), + }, + done: { + type: 'final', + }, + }, + }); + + expect(toXStateVisualization(machine)).toEqual({ + id: 'xstate-export', + initial: 'idle', + meta: { + agent: { + format: '@statelyai/agent/xstate-visualization', + runnable: false, + note: 'Generated for visualization. Runtime semantics remain in the agent machine.', + }, + }, + states: { + idle: { + on: { + submit: [ + { + target: 'working', + guard: { type: 'event.count > 0' }, + actions: ['assignContext', 'assignInput'], + meta: { + agent: { + event: 'submit', + updates: { + context: true, + input: true, + }, + }, + }, + }, + { + target: 'done', + guard: { type: '!(event.count > 0)' }, + meta: { + agent: { + event: 'submit', + }, + }, + }, + ], + }, + }, + working: { + invoke: { + id: 'invoke.working', + src: 'invoke.working', + onDone: { + target: 'done', + meta: { + agent: { + event: 'done.invoke.working', + }, + }, + }, + }, + meta: { + agent: { + invoke: true, + }, + }, + }, + done: { + type: 'final', + }, + }, + }); + expect(toXStateMachine(machine)).toEqual(toXStateVisualization(machine)); +}); + +test('exports always transitions for visualization', () => { + const machine = createAgentMachine({ + id: 'xstate-always', + context: () => ({}), + initial: 'checking', + states: { + checking: { + always: ({ messages }) => ({ + target: 'done', + messages: messages.concat({ role: 'assistant', content: 'ok' }), + }), + }, + done: { + type: 'final', + }, + }, + }); + + expect(toXStateVisualization(machine).states.checking).toEqual({ + always: { + target: 'done', + actions: ['assignMessages'], + meta: { + agent: { + event: '', + updates: { + messages: true, + }, + }, + }, + }, + }); +}); diff --git a/src/xstate/index.ts b/src/xstate/index.ts new file mode 100644 index 0000000..32ffda8 --- /dev/null +++ b/src/xstate/index.ts @@ -0,0 +1,216 @@ +import { toGraph, type AgentGraph, type AgentGraphEdge } from '../graph/index.js'; +import type { AgentMachine, MachineConfig, StateConfig } from '../types.js'; + +export interface XStateMachineConfig { + id: string; + initial?: string; + meta: { + agent: { + format: '@statelyai/agent/xstate-visualization'; + runnable: false; + note: string; + }; + }; + states: Record; +} + +export interface XStateStateConfig { + type?: 'final'; + on?: Record; + always?: XStateTransitionConfig | XStateTransitionConfig[]; + invoke?: { + id: string; + src: string; + onDone?: XStateTransitionConfig | XStateTransitionConfig[]; + }; + onDone?: XStateTransitionConfig | XStateTransitionConfig[]; + meta?: { + agent?: { + type?: 'choice'; + invoke?: boolean; + }; + }; +} + +export interface XStateTransitionConfig { + target: string; + guard?: { + type: string; + }; + actions?: string[]; + meta?: { + agent?: { + event?: string; + updates?: { + context?: boolean; + input?: boolean; + messages?: boolean; + }; + }; + }; +} + +type InternalMachine = AgentMachine & { + __config?: MachineConfig; +}; + +/** + * Convert an agent machine to a serializable XState-like machine config for + * visualization. Guards, actions, and invokes are symbolic metadata, so this + * object is not a runnable replacement for the agent machine. + */ +export function toXStateVisualization(machine: AgentMachine): XStateMachineConfig { + const config = (machine as InternalMachine).__config; + if (!config) { + throw new Error('Machine config metadata is unavailable for XState export'); + } + + const graph = toGraph(machine); + const states: Record = {}; + + for (const [stateId, state] of Object.entries(config.states)) { + const stateConfig = state as StateConfig; + const xstateState: XStateStateConfig = {}; + + if (stateConfig.type === 'final') { + xstateState.type = 'final'; + } + + const meta: NonNullable['agent'] = {}; + if (stateConfig.invoke) { + meta.invoke = true; + xstateState.invoke = { + id: `invoke.${stateId}`, + src: `invoke.${stateId}`, + }; + } + + const regularEdges = graph.edges.filter((edge) => + edge.sourceId === stateId + && edge.data.source !== 'invoke.done' + && edge.data.source !== 'always' + ); + + for (const [event, edges] of groupEdgesByEvent(regularEdges)) { + const formatted = formatTransitions(edges); + if (!formatted) { + continue; + } + + xstateState.on ??= {}; + xstateState.on[event] = formatted; + } + + if (stateConfig.always) { + const alwaysEdges = graph.edges.filter((edge) => + edge.sourceId === stateId + && edge.data.source === 'always' + ); + + const formattedAlways = formatTransitions(alwaysEdges); + if (formattedAlways) { + xstateState.always = formattedAlways; + } + } + + if (stateConfig.onDone) { + const doneEdges = graph.edges.filter((edge) => + edge.sourceId === stateId + && edge.data.source === 'invoke.done' + ); + + const formattedDone = formatTransitions(doneEdges); + if (formattedDone) { + if (xstateState.invoke) { + xstateState.invoke.onDone = formattedDone; + } else { + xstateState.onDone = formattedDone; + } + } + } + + if (Object.keys(meta).length > 0) { + xstateState.meta = { agent: meta }; + } + + states[stateId] = xstateState; + } + + return { + id: machine.id, + ...(typeof graph.initialNodeId === 'string' + ? { initial: graph.initialNodeId } + : {}), + meta: { + agent: { + format: '@statelyai/agent/xstate-visualization', + runnable: false, + note: 'Generated for visualization. Runtime semantics remain in the agent machine.', + }, + }, + states, + }; +} + +/** + * @deprecated Use `toXStateVisualization(...)` to make the visualization-only + * contract explicit. + */ +export const toXStateMachine = toXStateVisualization; + +function groupEdgesByEvent( + edges: AgentGraph['edges'] +): Map { + const grouped = new Map(); + + for (const edge of edges) { + const event = edge.data.event; + if (!event) { + continue; + } + + grouped.set(event, [...(grouped.get(event) ?? []), edge]); + } + + return grouped; +} + +function formatTransitions( + edges: AgentGraphEdge[] +): XStateTransitionConfig | XStateTransitionConfig[] | undefined { + const transitions = edges.map(formatTransition); + + if (transitions.length === 0) { + return undefined; + } + + return transitions.length === 1 ? transitions[0]! : transitions; +} + +function formatTransition(edge: AgentGraphEdge): XStateTransitionConfig { + const actions = [ + ...(edge.data.actions?.context ? ['assignContext'] : []), + ...(edge.data.actions?.input ? ['assignInput'] : []), + ...(edge.data.actions?.messages ? ['assignMessages'] : []), + ]; + + return { + target: edge.targetId, + ...(edge.data.guard ? { guard: edge.data.guard } : {}), + ...(actions.length > 0 ? { actions } : {}), + meta: { + agent: { + event: edge.data.event, + ...(edge.data.actions + ? { + updates: { + ...(edge.data.actions.context ? { context: true } : {}), + ...(edge.data.actions.input ? { input: true } : {}), + ...(edge.data.actions.messages ? { messages: true } : {}), + }, + } + : {}), + }, + }, + }; +} diff --git a/tsconfig.json b/tsconfig.json index e568eb1..68211cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,109 +1,18 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true /* Create source map files for emitted JavaScript files. */, - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - "noEmit": true /* Disable emitting files from a compilation. */, - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - "noUncheckedIndexedAccess": true /* Add 'undefined' to a type when accessed using an index. */, - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "noEmit": true, + "declaration": true, + "sourceMap": true, + "isolatedModules": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "examples"] } diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000..05fe1c3 --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + 'ai-sdk': 'src/ai-sdk/index.ts', + cloudflare: 'src/cloudflare/index.ts', + graph: 'src/graph/index.ts', + http: 'src/http/index.ts', + next: 'src/next/index.ts', + runtime: 'src/runtime/index.ts', + xstate: 'src/xstate/index.ts', + }, + format: ['esm', 'cjs'], + dts: true, + clean: true, +});