melony 0.1.55 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,111 +1,115 @@
1
1
  # melony
2
2
 
3
- Melony Core is a small **event-streaming runtime** for AI agents with first-class **Server‑Driven UI (SDUI)**.
4
-
5
- You write a `brain` (decides what to do) and `actions` (do work and stream events). Actions/brains can stream:
6
-
7
- - **Text** (`{ type: "text" }` / `{ type: "text-delta" }`)
8
- - **UI trees** (`event.ui = ui.card(...)`) that React (and other renderers) can display
9
- - Any custom events you want (`{ type: "tool-result", data: ... }`)
3
+ Fast, unopinionated, minimalist event-based framework for AI agents.
10
4
 
11
5
  ## Installation
12
6
 
13
7
  ```bash
14
- npm install melony zod
8
+ npm install melony
15
9
  ```
16
10
 
17
- ## 60-second usage
18
-
19
- ### 1) Define an agent runtime
11
+ ### 🔥 New: Fluent Builder API (Recommended)
20
12
 
21
13
  ```ts
22
- import { melony, action, ui } from "melony";
23
- import { z } from "zod";
24
-
25
- export const assistant = melony({
26
- actions: {
27
- searchProducts: action({
28
- name: "searchProducts",
29
- description: "Search products by keyword",
30
- paramsSchema: z.object({ query: z.string() }),
31
- execute: async function* ({ query }) {
32
- yield {
33
- type: "text",
34
- data: { content: `Searching for "${query}"...` },
35
- };
36
-
37
- // SDUI: stream real UI to the frontend
38
- yield {
39
- type: "ui",
40
- ui: ui.card({
41
- title: "Results",
42
- children: [
43
- ui.list([
44
- ui.listItem({ children: [ui.text("Product A — $10")] }),
45
- ui.listItem({ children: [ui.text("Product B — $12")] }),
46
- ]),
47
- ],
48
- }),
49
- };
50
- },
51
- }),
52
- },
14
+ import { melony } from "melony";
15
+
16
+ const agent = melony()
17
+ .action("getWeather", async function* ({ city }: { city: string }) {
18
+ yield { type: "text", data: { content: `Weather in ${city} is 24°C` } };
19
+ })
20
+ .on("text", async function* (event, { runtime }) {
21
+ if (event.data.content.includes("weather")) {
22
+ yield* runtime.execute("getWeather", { city: "London" });
23
+ }
24
+ });
53
25
 
54
- // The brain receives events and returns the next action to run.
55
- brain: async function* (event) {
56
- if (event.type === "text") {
57
- return {
58
- action: "searchProducts",
59
- params: { query: event.data?.content },
60
- };
26
+ // Run it (or use agent.stream(event) for HTTP)
27
+ for await (const event of agent.build().run({ type: "text", data: { content: "How's the weather?" } })) {
28
+ console.log(event);
29
+ }
30
+ ```
31
+
32
+ ### Legacy: Runtime Class API (Still Supported)
33
+
34
+ ```ts
35
+ import { MelonyRuntime } from "melony";
36
+
37
+ // 1. Create the runtime
38
+ const agent = new MelonyRuntime({
39
+ actions: {
40
+ getWeather: {
41
+ name: "getWeather",
42
+ execute: async function* ({ city }: { city: string }) {
43
+ yield { type: "text", data: { content: `Weather in ${city} is sunny!` } };
44
+ }
61
45
  }
62
46
  },
47
+ eventHandlers: new Map([
48
+ ["text", [async function* (event, { runtime }) {
49
+ yield* runtime.execute("getWeather", { city: "London" });
50
+ }]]
51
+ ])
63
52
  });
64
53
  ```
65
54
 
66
- ### 2) Serve it (Hono adapter)
55
+ ## Fluent Builder API
67
56
 
68
- ```ts
69
- import { Hono } from "hono";
70
- import { handle } from "melony/adapters/hono";
71
- import { assistant } from "./assistant";
57
+ The fluent builder provides an excellent developer experience with method chaining:
72
58
 
73
- const app = new Hono();
74
- app.post("/api/chat", handle(assistant));
59
+ ### Action Definition
60
+ ```ts
61
+ const agent = melony()
62
+ // Register an async generator with a name
63
+ .action("getWeather", async function* ({ city }) {
64
+ yield { type: "text", data: { content: `Weather in ${city} is sunny!` } };
65
+ });
75
66
  ```
76
67
 
77
- ### 3) Stream from the client
78
-
68
+ ### Event Handlers
79
69
  ```ts
80
- import { MelonyClient } from "melony/client";
81
-
82
- const client = new MelonyClient({
83
- url: "/api/chat",
84
- });
85
-
86
- for await (const event of client.sendEvent({
87
- type: "text",
88
- role: "user",
89
- data: { content: "running shoes" },
90
- })) {
91
- console.log("event:", event);
92
- }
70
+ const agent = melony()
71
+ .on("text", async function* (event, { runtime }) {
72
+ // Intercept and handle events
73
+ if (event.data.content.includes("help")) {
74
+ yield { type: "text", data: { content: "How can I help you?" } };
75
+ }
76
+ })
77
+ .on("action:before", async function* (event) {
78
+ console.log(`Executing action: ${event.data.action}`);
79
+ })
80
+ .build();
93
81
  ```
94
82
 
95
- ## Core concepts
83
+ ### Plugin System
84
+ Plugins allow you to modularize and reuse actions and handlers across different agents. A plugin is simply a function that receives the `MelonyBuilder`.
96
85
 
97
- - **Event**: the universal unit of streaming.
98
- - **Action**: an async generator that yields events and (optionally) returns a `NextAction`.
99
- - **Brain**: an async generator that decides the next action to run based on incoming events/results.
100
- - **Plugins/Hooks**: intercept runs, actions, and events (great place for HITL, logging, persistence).
86
+ ```ts
87
+ import { melony, MelonyPlugin } from "melony";
88
+
89
+ const loggingPlugin: MelonyPlugin = (builder) => {
90
+ builder.on("action:before", async function* (event) {
91
+ console.log(`[Plugin] Executing: ${event.data.action}`);
92
+ });
93
+ };
94
+
95
+ const agent = melony()
96
+ .use(loggingPlugin)
97
+ .action("greet", async function* () {
98
+ yield { type: "text", data: { content: "Hello!" } };
99
+ });
100
+ ```
101
101
 
102
- ## SDUI (Server‑Driven UI)
102
+ ### TypeScript Benefits
103
+ - **Full type inference** through the entire chain
104
+ - **IntelliSense** for all methods and parameters
105
+ - **Generic propagation** maintains type safety
106
+ - **Minimalist core** with zero required dependencies (except optional Zod)
103
107
 
104
- Melony ships a typed UI contract and builder:
108
+ ## Core Concepts
105
109
 
106
- - `ui.card(...)`, `ui.form(...)`, `ui.button(...)`, etc.
107
- - Put the resulting `UINode` into `event.ui`
108
- - `@melony/react` renders it automatically
110
+ - **Event**: The universal unit of streaming (`{ type, data, meta }`).
111
+ - **Action**: An async generator that yields events.
112
+ - **Event Handlers**: Reactive functions that listen to and emit events.
109
113
 
110
114
  ## License
111
115
 
package/dist/client.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { E as Event, C as Config } from './types-CE5iiNir.js';
2
- export { g as generateId } from './generate-id-DU8kwYc2.js';
3
- import 'zod';
1
+ import { E as Event } from './generate-id-DUrFIhxI.js';
2
+ export { j as generateId } from './generate-id-DUrFIhxI.js';
4
3
 
5
- interface ClientState {
6
- events: Event[];
4
+ interface ClientState<TEvent extends Event = Event> {
5
+ events: TEvent[];
7
6
  isLoading: boolean;
8
7
  error: Error | null;
9
8
  loadingStatus?: {
@@ -11,22 +10,22 @@ interface ClientState {
11
10
  details?: string;
12
11
  };
13
12
  }
14
- interface MelonyClientOptions {
13
+ interface MelonyClientOptions<TEvent extends Event = Event> {
15
14
  url: string;
16
- initialEvents?: Event[];
15
+ initialEvents?: TEvent[];
17
16
  headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
18
17
  }
19
- declare class MelonyClient {
18
+ declare class MelonyClient<TEvent extends Event = Event> {
20
19
  private state;
21
20
  readonly url: string;
22
21
  private headers?;
23
22
  private lastServerState;
24
23
  private abortController;
25
24
  private stateListeners;
26
- constructor(options: MelonyClientOptions);
27
- subscribe(listener: (state: ClientState) => void): () => void;
25
+ constructor(options: MelonyClientOptions<TEvent>);
26
+ subscribe(listener: (state: ClientState<TEvent>) => void): () => void;
28
27
  getState(): {
29
- events: Event[];
28
+ events: TEvent[];
30
29
  isLoading: boolean;
31
30
  error: Error | null;
32
31
  loadingStatus?: {
@@ -35,11 +34,10 @@ declare class MelonyClient {
35
34
  };
36
35
  };
37
36
  private getRequestHeaders;
38
- getConfig(): Promise<Config>;
39
37
  private setState;
40
- sendEvent(event: Event): AsyncGenerator<Event>;
38
+ sendEvent(event: TEvent): AsyncGenerator<TEvent>;
41
39
  private handleIncomingEvent;
42
- reset(events?: Event[]): void;
40
+ reset(events?: TEvent[]): void;
43
41
  }
44
42
 
45
43
  export { type ClientState, Event, MelonyClient, type MelonyClientOptions };
package/dist/client.js CHANGED
@@ -35,15 +35,6 @@ var MelonyClient = class {
35
35
  }
36
36
  return headers;
37
37
  }
38
- async getConfig() {
39
- const headers = await this.getRequestHeaders();
40
- const response = await fetch(this.url, {
41
- method: "GET",
42
- headers
43
- });
44
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
45
- return response.json();
46
- }
47
38
  setState(updates) {
48
39
  this.state = { ...this.state, ...updates };
49
40
  this.stateListeners.forEach((l) => l(this.getState()));
@@ -51,19 +42,22 @@ var MelonyClient = class {
51
42
  async *sendEvent(event) {
52
43
  if (this.abortController) this.abortController.abort();
53
44
  this.abortController = new AbortController();
54
- const runId = event.runId ?? generateId();
55
- const state = event.state ?? this.lastServerState;
45
+ const runId = event.meta?.runId ?? generateId();
46
+ const state = event.meta?.state ?? this.lastServerState;
56
47
  const optimisticEvent = {
57
48
  ...event,
58
- runId,
59
- state,
60
- role: event.role ?? "user",
61
- timestamp: event.timestamp ?? Date.now()
49
+ meta: {
50
+ ...event.meta,
51
+ id: event.meta?.id ?? generateId(),
52
+ runId,
53
+ state,
54
+ role: event.meta?.role ?? "user",
55
+ timestamp: event.meta?.timestamp ?? Date.now()
56
+ }
62
57
  };
63
58
  this.setState({
64
59
  isLoading: true,
65
60
  error: null,
66
- loadingStatus: void 0,
67
61
  events: [...this.state.events, optimisticEvent]
68
62
  });
69
63
  try {
@@ -97,51 +91,23 @@ var MelonyClient = class {
97
91
  }
98
92
  }
99
93
  }
100
- this.setState({ isLoading: false, loadingStatus: void 0 });
94
+ this.setState({ isLoading: false });
101
95
  } catch (err) {
102
96
  if (err instanceof Error && err.name === "AbortError") {
103
- this.setState({ isLoading: false, loadingStatus: void 0 });
97
+ this.setState({ isLoading: false });
104
98
  return;
105
99
  }
106
100
  const error = err instanceof Error ? err : new Error(String(err));
107
- this.setState({ error, isLoading: false, loadingStatus: void 0 });
101
+ this.setState({ error, isLoading: false });
108
102
  throw error;
109
103
  }
110
104
  }
111
105
  handleIncomingEvent(event) {
112
- if (event.state) {
113
- this.lastServerState = event.state;
106
+ if (event.meta?.state) {
107
+ this.lastServerState = event.meta.state;
114
108
  }
115
109
  const events = [...this.state.events];
116
- if (event.type === "loading-status") {
117
- const currentStatus = this.state.loadingStatus;
118
- const newMessage = event.data?.message ?? currentStatus?.message ?? "Processing...";
119
- const newDelta = event.data?.delta;
120
- let newDetails = currentStatus?.details ?? "";
121
- if (newDelta !== void 0) {
122
- newDetails += newDelta;
123
- } else if (event.data?.details !== void 0) {
124
- newDetails = event.data.details;
125
- }
126
- this.setState({
127
- loadingStatus: {
128
- message: newMessage,
129
- details: newDetails || void 0
130
- }
131
- });
132
- }
133
- const lastEvent = events[events.length - 1];
134
- if (event.type === "text-delta" && lastEvent?.type === "text-delta" && event.runId === lastEvent.runId && event.data?.delta) {
135
- events[events.length - 1] = {
136
- ...lastEvent,
137
- data: {
138
- ...lastEvent.data,
139
- delta: (lastEvent.data?.delta || "") + event.data.delta
140
- }
141
- };
142
- } else {
143
- events.push(event);
144
- }
110
+ events.push(event);
145
111
  this.setState({ events });
146
112
  }
147
113
  reset(events = []) {
@@ -149,8 +115,7 @@ var MelonyClient = class {
149
115
  this.setState({
150
116
  events,
151
117
  error: null,
152
- isLoading: false,
153
- loadingStatus: void 0
118
+ isLoading: false
154
119
  });
155
120
  }
156
121
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["import { Config, Event } from \"./types\";\nimport { generateId } from \"./utils/generate-id\";\n\nexport type { Event };\nexport { generateId };\n\nexport interface ClientState {\n events: Event[];\n isLoading: boolean;\n error: Error | null;\n loadingStatus?: {\n message: string;\n details?: string;\n };\n}\n\nexport interface MelonyClientOptions {\n url: string;\n initialEvents?: Event[];\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n}\n\nexport class MelonyClient {\n private state: ClientState;\n public readonly url: string;\n private headers?: MelonyClientOptions[\"headers\"];\n private lastServerState: any = null;\n private abortController: AbortController | null = null;\n private stateListeners: Set<(state: ClientState) => void> = new Set();\n\n constructor(options: MelonyClientOptions) {\n this.url = options.url;\n this.headers = options.headers;\n this.state = {\n events: options.initialEvents ?? [],\n isLoading: false,\n error: null,\n loadingStatus: undefined,\n };\n }\n\n subscribe(listener: (state: ClientState) => void) {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n getState() {\n return { ...this.state };\n }\n\n private async getRequestHeaders() {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.headers) {\n const extraHeaders =\n typeof this.headers === \"function\"\n ? await this.headers()\n : this.headers;\n Object.assign(headers, extraHeaders);\n }\n return headers;\n }\n\n async getConfig(): Promise<Config> {\n const headers = await this.getRequestHeaders();\n const response = await fetch(this.url, {\n method: \"GET\",\n headers,\n });\n\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n return response.json();\n }\n\n private setState(updates: Partial<ClientState>) {\n this.state = { ...this.state, ...updates };\n this.stateListeners.forEach((l) => l(this.getState()));\n }\n\n async *sendEvent(event: Event): AsyncGenerator<Event> {\n if (this.abortController) this.abortController.abort();\n this.abortController = new AbortController();\n\n const runId = event.runId ?? generateId();\n const state = event.state ?? this.lastServerState;\n const optimisticEvent: Event = {\n ...event,\n runId,\n state,\n role: event.role ?? \"user\",\n timestamp: event.timestamp ?? Date.now(),\n };\n\n this.setState({\n isLoading: true,\n error: null,\n loadingStatus: undefined,\n events: [...this.state.events, optimisticEvent],\n });\n\n try {\n const headers = await this.getRequestHeaders();\n const response = await fetch(this.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ event: optimisticEvent }),\n signal: this.abortController.signal,\n });\n\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n if (!response.body) throw new Error(\"No response body\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n try {\n const incomingEvent: Event = JSON.parse(line.slice(6));\n this.handleIncomingEvent(incomingEvent);\n yield incomingEvent;\n } catch (e) {\n console.error(\"Failed to parse event\", e);\n }\n }\n }\n this.setState({ isLoading: false, loadingStatus: undefined });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n this.setState({ isLoading: false, loadingStatus: undefined });\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n this.setState({ error, isLoading: false, loadingStatus: undefined });\n throw error;\n }\n }\n\n private handleIncomingEvent(event: Event) {\n if (event.state) {\n this.lastServerState = event.state;\n }\n const events = [...this.state.events];\n\n if (event.type === \"loading-status\") {\n const currentStatus = this.state.loadingStatus;\n const newMessage =\n event.data?.message ?? currentStatus?.message ?? \"Processing...\";\n const newDelta = event.data?.delta;\n\n let newDetails = currentStatus?.details ?? \"\";\n if (newDelta !== undefined) {\n newDetails += newDelta;\n } else if (event.data?.details !== undefined) {\n newDetails = event.data.details;\n }\n\n this.setState({\n loadingStatus: {\n message: newMessage,\n details: newDetails || undefined,\n },\n });\n }\n\n const lastEvent = events[events.length - 1];\n if (\n event.type === \"text-delta\" &&\n lastEvent?.type === \"text-delta\" &&\n event.runId === lastEvent.runId &&\n event.data?.delta\n ) {\n events[events.length - 1] = {\n ...lastEvent,\n data: {\n ...lastEvent.data,\n delta: (lastEvent.data?.delta || \"\") + event.data.delta,\n },\n };\n } else {\n events.push(event);\n }\n\n this.setState({ events });\n }\n\n reset(events: Event[] = []) {\n if (this.abortController) this.abortController.abort();\n this.setState({\n events,\n error: null,\n isLoading: false,\n loadingStatus: undefined,\n });\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["import { Event } from \"./types\";\nimport { generateId } from \"./utils/generate-id\";\n\nexport type { Event };\nexport { generateId };\n\nexport interface ClientState<TEvent extends Event = Event> {\n events: TEvent[];\n isLoading: boolean;\n error: Error | null;\n loadingStatus?: {\n message: string;\n details?: string;\n };\n}\n\nexport interface MelonyClientOptions<TEvent extends Event = Event> {\n url: string;\n initialEvents?: TEvent[];\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n}\n\nexport class MelonyClient<TEvent extends Event = Event> {\n private state: ClientState<TEvent>;\n public readonly url: string;\n private headers?: MelonyClientOptions<TEvent>[\"headers\"];\n private lastServerState: any = null;\n private abortController: AbortController | null = null;\n private stateListeners: Set<(state: ClientState<TEvent>) => void> = new Set();\n\n constructor(options: MelonyClientOptions<TEvent>) {\n this.url = options.url;\n this.headers = options.headers;\n this.state = {\n events: options.initialEvents ?? [],\n isLoading: false,\n error: null,\n loadingStatus: undefined,\n };\n }\n\n subscribe(listener: (state: ClientState<TEvent>) => void) {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n getState() {\n return { ...this.state };\n }\n\n private async getRequestHeaders() {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.headers) {\n const extraHeaders =\n typeof this.headers === \"function\"\n ? await this.headers()\n : this.headers;\n Object.assign(headers, extraHeaders);\n }\n return headers;\n }\n\n private setState(updates: Partial<ClientState<TEvent>>) {\n this.state = { ...this.state, ...updates };\n this.stateListeners.forEach((l) => l(this.getState()));\n }\n\n async *sendEvent(event: TEvent): AsyncGenerator<TEvent> {\n if (this.abortController) this.abortController.abort();\n this.abortController = new AbortController();\n\n const runId = event.meta?.runId ?? generateId();\n const state = event.meta?.state ?? this.lastServerState;\n const optimisticEvent: TEvent = {\n ...event,\n meta: {\n ...event.meta,\n id: event.meta?.id ?? generateId(),\n runId,\n state,\n role: event.meta?.role ?? \"user\",\n timestamp: event.meta?.timestamp ?? Date.now(),\n },\n } as TEvent;\n\n this.setState({\n isLoading: true,\n error: null,\n events: [...this.state.events, optimisticEvent],\n });\n\n try {\n const headers = await this.getRequestHeaders();\n const response = await fetch(this.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ event: optimisticEvent }),\n signal: this.abortController.signal,\n });\n\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n if (!response.body) throw new Error(\"No response body\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n try {\n const incomingEvent: TEvent = JSON.parse(line.slice(6));\n this.handleIncomingEvent(incomingEvent);\n yield incomingEvent;\n } catch (e) {\n console.error(\"Failed to parse event\", e);\n }\n }\n }\n this.setState({ isLoading: false });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n this.setState({ isLoading: false });\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n this.setState({ error, isLoading: false });\n throw error;\n }\n }\n\n private handleIncomingEvent(event: TEvent) {\n if (event.meta?.state) {\n this.lastServerState = event.meta.state;\n }\n\n const events = [...this.state.events];\n\n events.push(event);\n\n this.setState({ events });\n }\n\n reset(events: TEvent[] = []) {\n if (this.abortController) this.abortController.abort();\n this.setState({\n events,\n error: null,\n isLoading: false,\n });\n }\n}\n"]}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The Melony Runtime.
3
+ * Fully unopinionated - executes a single action per run.
4
+ * Chaining/looping is left to userland.
5
+ */
6
+ declare class Runtime<TState = any, TEvent extends Event = Event> {
7
+ readonly config: Config<TState, TEvent>;
8
+ private queue;
9
+ private isEmitting;
10
+ constructor(config: Config<TState, TEvent>);
11
+ /**
12
+ * Run an action by name with given params.
13
+ * This is the primary way to execute actions.
14
+ */
15
+ execute(actionName: string, params: any, options?: {
16
+ state?: TState;
17
+ runId?: string;
18
+ }): AsyncGenerator<TEvent>;
19
+ /**
20
+ * Process an incoming event through the runtime.
21
+ * All event processing is handled by user-defined event handlers.
22
+ */
23
+ run(event: TEvent): AsyncGenerator<TEvent>;
24
+ /**
25
+ * Run all event handlers that match the given event type.
26
+ */
27
+ private runEventHandlers;
28
+ /**
29
+ * Internal helper to yield an event with metadata.
30
+ * Handlers are invoked by runEventHandlers when events bubble up.
31
+ */
32
+ private emit;
33
+ }
34
+
35
+ type Role = "user" | "assistant" | "system";
36
+ /**
37
+ * System-managed metadata for an event.
38
+ */
39
+ interface EventMeta<TState = any> {
40
+ id: string;
41
+ runId: string;
42
+ timestamp: number;
43
+ role: Role;
44
+ state: TState;
45
+ [key: string]: any;
46
+ }
47
+ /**
48
+ * The core Event structure.
49
+ * Fully unopinionated - just type, data, and optional metadata.
50
+ */
51
+ type Event<TData = any> = {
52
+ type: string;
53
+ data: TData;
54
+ meta?: EventMeta;
55
+ };
56
+ /**
57
+ * Built-in action lifecycle events emitted by the runtime.
58
+ */
59
+ interface ActionBeforeEvent extends Event<{
60
+ action: string;
61
+ params: any;
62
+ }> {
63
+ type: "action:before";
64
+ }
65
+ interface ActionAfterEvent extends Event<{
66
+ action: string;
67
+ result: any;
68
+ }> {
69
+ type: "action:after";
70
+ }
71
+ /**
72
+ * Built-in call-action event for triggering action execution.
73
+ */
74
+ interface CallActionEvent extends Event<{
75
+ action: string;
76
+ params: unknown;
77
+ }> {
78
+ type: "call-action";
79
+ }
80
+ /**
81
+ * Built-in error event emitted by the runtime.
82
+ */
83
+ interface ErrorEvent extends Event<{
84
+ message: string;
85
+ action?: string;
86
+ stack?: string;
87
+ }> {
88
+ type: "error";
89
+ }
90
+ type ActionExecute<TParams = any, TState = any, TEvent extends Event = Event> = (params: TParams, context: RuntimeContext<TState, TEvent>) => AsyncGenerator<TEvent, any, unknown>;
91
+ interface Action<TParams = any, TState = any, TEvent extends Event = Event> {
92
+ name: string;
93
+ execute: ActionExecute<TParams, TState, TEvent>;
94
+ }
95
+ interface RuntimeContext<TState = any, TEvent extends Event = Event> {
96
+ state: TState;
97
+ runId: string;
98
+ actions: Record<string, Action<any, TState, TEvent>>;
99
+ runtime: Runtime<TState, TEvent>;
100
+ /**
101
+ * Immediately interrupts the runtime execution.
102
+ * If an event is provided, it will be emitted before the runtime stops.
103
+ */
104
+ suspend: (event?: TEvent) => never;
105
+ }
106
+ /**
107
+ * Event handler function for processing events.
108
+ * Can return events to emit or undefined to continue processing.
109
+ */
110
+ type EventHandler<TState = any, TEvent extends Event = Event> = (event: TEvent, context: RuntimeContext<TState, TEvent>) => AsyncGenerator<TEvent, void, unknown> | void;
111
+ interface Config<TState = any, TEvent extends Event = Event> {
112
+ actions: Record<string, Action<any, TState, TEvent>>;
113
+ eventHandlers: Map<string, EventHandler<TState, TEvent>[]>;
114
+ }
115
+
116
+ declare const generateId: () => string;
117
+
118
+ export { type ActionExecute as A, type Config as C, type Event as E, Runtime as R, type Action as a, type RuntimeContext as b, type Role as c, type EventMeta as d, type ActionBeforeEvent as e, type ActionAfterEvent as f, type CallActionEvent as g, type ErrorEvent as h, type EventHandler as i, generateId as j };
package/dist/index.d.ts CHANGED
@@ -1,53 +1,76 @@
1
- import { C as Config, E as Event, A as Action, B as Brain, P as Plugin, M as Message } from './types-CE5iiNir.js';
2
- export { l as ActionExecute, H as HookGenerator, n as Hooks, N as NextAction, R as Role, m as RuntimeContext, a as UIAlign, e as UIColor, j as UIContract, b as UIJustify, k as UINode, d as UIOrientation, i as UIRadius, h as UIShadow, U as UISize, f as UISpacing, g as UIWidth, c as UIWrap, u as ui } from './types-CE5iiNir.js';
3
- import { z } from 'zod';
4
- export { g as generateId } from './generate-id-DU8kwYc2.js';
1
+ import { R as Runtime, A as ActionExecute, a as Action, E as Event, C as Config, b as RuntimeContext } from './generate-id-DUrFIhxI.js';
2
+ export { f as ActionAfterEvent, e as ActionBeforeEvent, g as CallActionEvent, h as ErrorEvent, i as EventHandler, d as EventMeta, c as Role, j as generateId } from './generate-id-DUrFIhxI.js';
3
+
4
+ declare const MelonyRuntime: typeof Runtime;
5
5
 
6
6
  /**
7
- * The Slim Runtime.
8
- * Single Responsibility: Orchestrate Event -> Action -> Event transitions.
7
+ * Create an action with just a name and handler.
8
+ * Compatible with any agent - types are resolved automatically when added to an agent.
9
9
  */
10
- declare class Runtime<TState = any> {
11
- private config;
12
- constructor(config: Config<TState>);
13
- run(event: Event): AsyncGenerator<Event>;
14
- private dispatchToBrain;
15
- private executeAction;
16
- /**
17
- * Internal helper to call a hook (generator) and yield its events.
18
- */
19
- private callHook;
20
- /**
21
- * Internal helper to yield an event and trigger the onEvent hook.
22
- */
23
- private emit;
24
- }
25
-
26
- declare const melony: <TState = any>(config: Config<TState>) => {
27
- config: Config<TState>;
28
- run: (event: Event) => AsyncGenerator<Event>;
29
- };
30
-
10
+ declare function action<TParams = any>(name: string, execute: ActionExecute<TParams, any, any>): Action<TParams, any, any>;
31
11
  /**
32
- * Helper to define an action with full type inference.
12
+ * Helper to define an action with full type inference (for advanced use cases).
33
13
  */
34
- declare const action: <T extends z.ZodSchema, TState = any>(config: Action<T, TState>) => Action<T, TState>;
14
+ declare function action<TParams = any, TState = any, TEvent extends Event = Event>(config: Action<TParams, TState, TEvent>): Action<TParams, TState, TEvent>;
35
15
 
36
16
  /**
37
- * A Cortex is a collection of Brain Regions (Lobes).
38
- * It delegates events to the appropriate region and coordinates the agentic loop.
17
+ * A Melony plugin is a function that receives the builder and extends it.
18
+ * This allows for modularizing common actions and handlers.
39
19
  */
40
- declare function cortex<TState = any>(regions: Record<string, Brain<TState>>): Brain<TState>;
20
+ type MelonyPlugin<TState = any, TEvent extends Event = Event> = (builder: MelonyBuilder<TState, TEvent>) => void;
41
21
  /**
42
- * Helper to define a lobe with full type inference.
43
- * Lobes are just Brain functions but named for clarity in the biological theme.
22
+ * Fluent builder for creating Melony agents with excellent developer experience.
23
+ * Provides method chaining for actions and plugins with full TypeScript support.
44
24
  */
45
- declare const lobe: <TState = any>(brain: Brain<TState>) => Brain<TState>;
46
-
25
+ declare class MelonyBuilder<TState = any, TEvent extends Event = Event> {
26
+ private config;
27
+ constructor(initialConfig?: Partial<Config<TState, TEvent>>);
28
+ /**
29
+ * Add built-in event handlers that are available in all agents.
30
+ */
31
+ private addBuiltInHandlers;
32
+ /**
33
+ * Add an action to the agent with fluent method chaining.
34
+ * Supports two patterns:
35
+ * - Pass a pre-defined Action object
36
+ * - Define action inline with name and handler
37
+ */
38
+ action<TParams = any>(action: Action<TParams, any, any>): this;
39
+ action<TParams = any>(name: string, execute: ActionExecute<TParams, any, any>): this;
40
+ /**
41
+ * Add an event handler for a specific event type. Supports method chaining.
42
+ * The handler receives the narrowed event type based on the eventType string.
43
+ */
44
+ on<K extends TEvent["type"]>(eventType: K, handler: (event: Extract<TEvent, {
45
+ type: K;
46
+ }>, context: RuntimeContext<TState, TEvent>) => AsyncGenerator<TEvent, void, unknown> | void): this;
47
+ /**
48
+ * Use a plugin to extend the builder.
49
+ * This is ideal for modularizing common actions and handlers.
50
+ */
51
+ use(plugin: MelonyPlugin<TState, TEvent>): this;
52
+ /**
53
+ * Build and return the Melony runtime instance.
54
+ * This is the final method in the fluent chain.
55
+ */
56
+ build(): Runtime<TState, TEvent>;
57
+ /**
58
+ * Execute and stream the response for an event.
59
+ * This is a convenience method that builds the runtime and calls run().
60
+ */
61
+ stream(event: TEvent, options?: {
62
+ state?: TState;
63
+ }): Promise<Response>;
64
+ /**
65
+ * Get the current configuration (useful for debugging or serialization).
66
+ */
67
+ getConfig(): Config<TState, TEvent>;
68
+ }
47
69
  /**
48
- * Helper to define a plugin.
70
+ * Factory function to create a new Melony builder instance.
71
+ * This is the entry point for the fluent API.
49
72
  */
50
- declare const plugin: <TState = any>(config: Plugin<TState>) => Plugin<TState>;
73
+ declare function melony<TState = any, TEvent extends Event = Event>(initialConfig?: Partial<Config<TState, TEvent>>): MelonyBuilder<TState, TEvent>;
51
74
 
52
75
  /**
53
76
  * Convert an async generator of events to an HTTP streaming response
@@ -55,13 +78,4 @@ declare const plugin: <TState = any>(config: Plugin<TState>) => Plugin<TState>;
55
78
  */
56
79
  declare function createStreamResponse(generator: AsyncGenerator<Event>): Response;
57
80
 
58
- declare function convertEventsToMessages(events: Event[]): Message[];
59
-
60
- /**
61
- * Filters a list of events by their slot property.
62
- * If multiple events have the same slot, only the latest one is kept,
63
- * but its position in the returned array corresponds to the first time that slot appeared.
64
- */
65
- declare function filterEventsBySlots(events: Event[]): Event[];
66
-
67
- export { Action, Brain, Config, Event, Message, Plugin, Runtime, action, convertEventsToMessages, cortex, createStreamResponse, filterEventsBySlots, lobe, melony, plugin };
81
+ export { Action, ActionExecute, Config, Event, MelonyBuilder, type MelonyPlugin, MelonyRuntime, Runtime, RuntimeContext, action, createStreamResponse, melony };