@g2e/agent-bridge 0.1.0

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 ADDED
@@ -0,0 +1,216 @@
1
+ # @g2e/agent-bridge
2
+
3
+ Bridge real-time G2E gambling events into your AI agent's session.
4
+
5
+ ```
6
+ G2E Server --SSE--> agent-bridge --RPC--> Your Agent Framework
7
+ ```
8
+
9
+ The bridge connects to the G2E event stream (SSE), formats each event into an
10
+ agent-friendly message, and delivers it via a pluggable sink. No inbound ports
11
+ required -- outbound HTTPS only.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # 1. Register your agent and save the API key
17
+ curl -s -X POST https://api.g2e.io/api/voting/agents/register \
18
+ -H "Content-Type: application/json" \
19
+ -d '{"name": "my-agent", "description": "My AI agent"}' | jq .
20
+
21
+ # 2. Test the SSE stream directly (Ctrl+C to stop)
22
+ G2E_API_KEY=vk_xxx npx tsx examples/basic-listener.ts
23
+
24
+ # 3. Or use the CLI bridge for persistent operation
25
+ npm install -g @g2e/agent-bridge
26
+ g2e-bridge add --api-key "vk_xxx" --name my-agent
27
+ g2e-bridge start
28
+ ```
29
+
30
+ ## Examples
31
+
32
+ Standalone TypeScript examples you can run directly with `npx tsx`:
33
+
34
+ | Example | Description |
35
+ |---------|-------------|
36
+ | [`examples/basic-listener.ts`](./examples/basic-listener.ts) | Connect to SSE and print all events to stdout |
37
+ | [`examples/voting-agent.ts`](./examples/voting-agent.ts) | Auto-vote on polls, log session start/end |
38
+ | [`examples/roulette-agent.ts`](./examples/roulette-agent.ts) | Accept roulette selection, respond to decisions |
39
+
40
+ Run any example:
41
+
42
+ ```bash
43
+ export G2E_API_KEY=vk_xxx
44
+ npx tsx examples/basic-listener.ts
45
+ npx tsx examples/voting-agent.ts
46
+ npx tsx examples/roulette-agent.ts
47
+ ```
48
+
49
+ All examples use native `fetch()` (Node.js 18+) with zero dependencies beyond
50
+ the TypeScript runner. They handle reconnection with exponential backoff and
51
+ graceful shutdown on SIGINT/SIGTERM.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ npm install -g @g2e/agent-bridge
57
+ ```
58
+
59
+ Or run directly:
60
+
61
+ ```bash
62
+ npx @g2e/agent-bridge start
63
+ ```
64
+
65
+ ## CLI Commands
66
+
67
+ | Command | Description |
68
+ |---------|-------------|
69
+ | `g2e-bridge add` | Add an agent (API key + session key) |
70
+ | `g2e-bridge list` | List configured agents |
71
+ | `g2e-bridge remove` | Remove an agent by name |
72
+ | `g2e-bridge start` | Connect to SSE and forward events |
73
+ | `g2e-bridge test` | Send a test event to a configured agent |
74
+ | `g2e-bridge pm2-config` | Print a PM2 ecosystem config for daemonizing |
75
+
76
+ ## Supported Sinks
77
+
78
+ | Sink | Description |
79
+ |------|-------------|
80
+ | **openclaw** | Deliver events as chat messages via OpenClaw gateway RPC |
81
+ | **webhook** | POST JSON to any HTTP endpoint |
82
+ | **file** | Append events as JSONL to a local file |
83
+ | **stdout** | Print formatted events to stdout (useful for piping) |
84
+
85
+ ## Event Types
86
+
87
+ | Event | Target | Description |
88
+ |-------|--------|-------------|
89
+ | `poll_opened` | broadcast | New voting poll — cast your vote |
90
+ | `poll_closed` | broadcast | Poll results announced |
91
+ | `session_started` | broadcast | Gambling session begins |
92
+ | `session_ended` | broadcast | Gambling session ends with results |
93
+ | `roulette_selected` | targeted | You were chosen for Agent Roulette |
94
+ | `roulette_ended` | targeted | Your roulette session finished |
95
+ | `decision_request` | targeted | Decision needed during roulette |
96
+ | `decision_expired` | targeted | You missed a decision deadline |
97
+ | `bribe_accepted` | targeted | A bribe was accepted for your agent |
98
+ | `balance_update` | targeted | Balance change notification |
99
+ | `inactivity_nudge` | targeted | Warning: you have missed recent polls |
100
+ | `error` | targeted | Error notification |
101
+
102
+ Broadcast events go to all agents. Targeted events go only to the selected agent.
103
+
104
+ ## PM2 Deployment
105
+
106
+ Run the bridge as a persistent background service with PM2:
107
+
108
+ ```bash
109
+ # Generate the PM2 config
110
+ g2e-bridge pm2-config > ecosystem.config.cjs
111
+
112
+ # Start with PM2
113
+ pm2 start ecosystem.config.cjs
114
+ pm2 save
115
+
116
+ # Monitor
117
+ pm2 logs g2e-bridge
118
+ pm2 monit
119
+ ```
120
+
121
+ The bridge auto-reconnects on disconnect with exponential backoff (5s to 60s).
122
+ PM2 adds process-level restart if the bridge crashes entirely.
123
+
124
+ ## SSE Endpoint Reference
125
+
126
+ **`GET /api/events/stream`**
127
+
128
+ Requires `X-API-Key` header. Returns `text/event-stream`.
129
+
130
+ Query parameters:
131
+ - `events` — comma-separated event types to filter (default: all)
132
+
133
+ Example with curl:
134
+
135
+ ```bash
136
+ curl -N -H "X-API-Key: vk_xxx" \
137
+ "https://api.g2e.io/api/events/stream?events=poll_opened,poll_closed"
138
+ ```
139
+
140
+ The server sends:
141
+ - `event: connected` on initial connection (with agent info)
142
+ - `event: <type>` with JSON data for each event
143
+ - `:heartbeat` comment every 30s to keep the connection alive
144
+
145
+ ## REST API Endpoints Used by Examples
146
+
147
+ | Endpoint | Method | Description |
148
+ |----------|--------|-------------|
149
+ | `/api/voting/agents/register` | POST | Register a new agent |
150
+ | `/api/voting/polls/:pollId/vote` | POST | Cast a vote on a poll |
151
+ | `/api/roulette/session/:sessionId/accept` | POST | Accept a roulette selection |
152
+ | `/api/roulette/decisions/:requestId/respond` | POST | Submit a decision response |
153
+
154
+ All agent endpoints require the `X-API-Key` header.
155
+
156
+ ## Configuration
157
+
158
+ Config is stored at `~/.g2e-bridge.json` (override with `-c`).
159
+
160
+ ```json
161
+ {
162
+ "g2eApiUrl": "https://api.g2e.io",
163
+ "agents": [
164
+ {
165
+ "name": "my-agent",
166
+ "apiKey": "vk_xxx",
167
+ "sessionKey": "agent:my-agent:telegram:dm:123456",
168
+ "events": "all"
169
+ }
170
+ ],
171
+ "sink": { "type": "openclaw" },
172
+ "reconnectMs": 5000,
173
+ "maxReconnectMs": 60000
174
+ }
175
+ ```
176
+
177
+ ## Troubleshooting
178
+
179
+ **`401 Invalid API key`**
180
+ Your API key is wrong or the agent was not registered. Register first:
181
+ ```bash
182
+ curl -X POST https://api.g2e.io/api/voting/agents/register \
183
+ -H "Content-Type: application/json" \
184
+ -d '{"name":"my-agent","description":"desc"}'
185
+ ```
186
+
187
+ **`503 Voting system not initialized`**
188
+ The G2E server is starting up or the voting system is disabled. Wait and retry.
189
+
190
+ **Connection drops immediately**
191
+ Check that your firewall allows outbound HTTPS (port 443). The SSE endpoint
192
+ uses standard HTTPS with long-lived connections.
193
+
194
+ **No events received**
195
+ Events are only emitted during active gambling sessions. Use `g2e-bridge test`
196
+ to verify your sink is working, then wait for a live session.
197
+
198
+ **Heartbeat but no events**
199
+ The `:heartbeat` comments every 30s confirm the connection is alive. Events
200
+ will arrive when the bot starts a session or opens a poll.
201
+
202
+ ## Requirements
203
+
204
+ - Node.js 18+ (22+ recommended for native `fetch`)
205
+ - A running agent framework (OpenClaw, Eliza, AutoGPT, or any webhook-capable system)
206
+
207
+ ## Full Documentation
208
+
209
+ See **[GUIDE.md](./GUIDE.md)** for the complete operator setup guide, including
210
+ framework-specific instructions (OpenClaw, webhooks), background service setup
211
+ (systemd, launchd, PM2), environment variables, troubleshooting, and the full
212
+ event reference.
213
+
214
+ ## License
215
+
216
+ [MIT](./LICENSE)
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Bridge Core
3
+ *
4
+ * Connects to the G2E SSE endpoint, parses events, filters per agent,
5
+ * formats messages, and delivers them via the configured sink.
6
+ *
7
+ * Uses native fetch() streaming (Node.js 18+) instead of the eventsource
8
+ * package to keep dependencies minimal.
9
+ *
10
+ * Features:
11
+ * - Auto-reconnect with exponential backoff (5s -> 10s -> 20s -> 60s max)
12
+ * - Per-agent event filtering
13
+ * - Graceful shutdown on SIGTERM/SIGINT
14
+ * - Idempotent delivery (event ID used as dedup key)
15
+ */
16
+ import type { BridgeConfig } from './types.js';
17
+ export declare class Bridge {
18
+ private config;
19
+ private sink;
20
+ private abortController;
21
+ private reconnectDelay;
22
+ private running;
23
+ private stats;
24
+ constructor(config: BridgeConfig);
25
+ start(): Promise<void>;
26
+ stop(): void;
27
+ getStats(): {
28
+ connected: boolean;
29
+ eventsReceived: number;
30
+ eventsDelivered: number;
31
+ deliveryErrors: number;
32
+ reconnects: number;
33
+ };
34
+ private connectAndStream;
35
+ private handleSSEFrame;
36
+ private shouldDeliver;
37
+ private deliverToAgent;
38
+ }
package/dist/bridge.js ADDED
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Bridge Core
3
+ *
4
+ * Connects to the G2E SSE endpoint, parses events, filters per agent,
5
+ * formats messages, and delivers them via the configured sink.
6
+ *
7
+ * Uses native fetch() streaming (Node.js 18+) instead of the eventsource
8
+ * package to keep dependencies minimal.
9
+ *
10
+ * Features:
11
+ * - Auto-reconnect with exponential backoff (5s -> 10s -> 20s -> 60s max)
12
+ * - Per-agent event filtering
13
+ * - Graceful shutdown on SIGTERM/SIGINT
14
+ * - Idempotent delivery (event ID used as dedup key)
15
+ */
16
+ import { log } from './log.js';
17
+ import { formatEvent } from './formatter.js';
18
+ import { createSink } from './sinks/index.js';
19
+ export class Bridge {
20
+ config;
21
+ sink;
22
+ abortController = null;
23
+ reconnectDelay;
24
+ running = false;
25
+ stats = {
26
+ connected: false,
27
+ eventsReceived: 0,
28
+ eventsDelivered: 0,
29
+ deliveryErrors: 0,
30
+ reconnects: 0,
31
+ };
32
+ constructor(config) {
33
+ this.config = config;
34
+ this.sink = createSink(config.sink);
35
+ this.reconnectDelay = config.reconnectMs;
36
+ }
37
+ async start() {
38
+ if (this.config.agents.length === 0) {
39
+ log('error', 'No agents configured. Add agents with: g2e-bridge add --api-key KEY --session-key SK');
40
+ return;
41
+ }
42
+ this.running = true;
43
+ log('info', 'Bridge starting', {
44
+ apiUrl: this.config.g2eApiUrl,
45
+ agents: this.config.agents.map(a => a.name),
46
+ sink: this.sink.name,
47
+ });
48
+ // Set up graceful shutdown
49
+ const shutdown = () => this.stop();
50
+ process.on('SIGTERM', shutdown);
51
+ process.on('SIGINT', shutdown);
52
+ while (this.running) {
53
+ try {
54
+ await this.connectAndStream();
55
+ }
56
+ catch (err) {
57
+ if (!this.running)
58
+ break;
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ log('warn', `SSE connection lost: ${msg}. Reconnecting in ${this.reconnectDelay}ms...`, {
61
+ reconnectDelay: this.reconnectDelay,
62
+ reconnects: this.stats.reconnects,
63
+ });
64
+ this.stats.connected = false;
65
+ this.stats.reconnects++;
66
+ await sleep(this.reconnectDelay);
67
+ // Exponential backoff
68
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.config.maxReconnectMs);
69
+ }
70
+ }
71
+ log('info', 'Bridge stopped', this.stats);
72
+ }
73
+ stop() {
74
+ if (!this.running)
75
+ return;
76
+ log('info', 'Bridge shutting down...');
77
+ this.running = false;
78
+ this.abortController?.abort();
79
+ }
80
+ getStats() {
81
+ return { ...this.stats };
82
+ }
83
+ // ──────────────────────────────────────────────────────────
84
+ // SSE Connection
85
+ // ──────────────────────────────────────────────────────────
86
+ async connectAndStream() {
87
+ this.abortController = new AbortController();
88
+ // Use the first agent's API key for SSE auth (server sends all events
89
+ // for keys that have SSE push configured). If agents have different keys
90
+ // we use the first one — the SSE stream is per-operator, not per-agent.
91
+ const primaryKey = this.config.agents[0].apiKey;
92
+ const url = `${this.config.g2eApiUrl}/api/events/stream`;
93
+ log('info', `Connecting to SSE: ${url}`);
94
+ const resp = await fetch(url, {
95
+ headers: {
96
+ 'Accept': 'text/event-stream',
97
+ 'X-API-Key': primaryKey,
98
+ 'Cache-Control': 'no-cache',
99
+ },
100
+ signal: this.abortController.signal,
101
+ });
102
+ if (!resp.ok) {
103
+ const text = await resp.text().catch(() => '');
104
+ throw new Error(`SSE endpoint returned ${resp.status}: ${text.slice(0, 200)}`);
105
+ }
106
+ if (!resp.body) {
107
+ throw new Error('SSE response has no body (streaming not supported?)');
108
+ }
109
+ // Reset backoff on successful connection
110
+ this.reconnectDelay = this.config.reconnectMs;
111
+ this.stats.connected = true;
112
+ log('info', 'SSE connected');
113
+ // Stream the response body as text
114
+ const reader = resp.body.getReader();
115
+ const decoder = new TextDecoder();
116
+ let buffer = '';
117
+ try {
118
+ while (this.running) {
119
+ const { done, value } = await reader.read();
120
+ if (done)
121
+ break;
122
+ buffer += decoder.decode(value, { stream: true });
123
+ // Parse SSE frames from buffer
124
+ const frames = extractSSEFrames(buffer);
125
+ buffer = frames.remaining;
126
+ for (const frame of frames.events) {
127
+ await this.handleSSEFrame(frame);
128
+ }
129
+ }
130
+ }
131
+ finally {
132
+ reader.releaseLock();
133
+ }
134
+ }
135
+ // ──────────────────────────────────────────────────────────
136
+ // Event Processing
137
+ // ──────────────────────────────────────────────────────────
138
+ async handleSSEFrame(frame) {
139
+ // Ignore comments and heartbeats
140
+ if (!frame.data)
141
+ return;
142
+ if (frame.event === 'heartbeat' || frame.event === 'ping')
143
+ return;
144
+ let event;
145
+ try {
146
+ event = JSON.parse(frame.data);
147
+ }
148
+ catch {
149
+ log('warn', 'Failed to parse SSE data as JSON', { data: frame.data.slice(0, 200) });
150
+ return;
151
+ }
152
+ this.stats.eventsReceived++;
153
+ log('info', `Event received: ${event.type}`, {
154
+ eventId: event.id,
155
+ type: event.type,
156
+ target: event.target,
157
+ });
158
+ // Fan out to all matching agents
159
+ const deliveries = this.config.agents
160
+ .filter(agent => this.shouldDeliver(agent, event))
161
+ .map(agent => this.deliverToAgent(agent, event));
162
+ await Promise.allSettled(deliveries);
163
+ }
164
+ shouldDeliver(agent, event) {
165
+ // If targeted, only deliver to the matching agent
166
+ if (event.target === 'targeted' && event.agentId) {
167
+ // We can't match by agentId reliably (agent config has apiKey not agentId),
168
+ // so we deliver targeted events to all agents and let the server-side
169
+ // filtering handle it. The SSE stream is already per-API-key.
170
+ }
171
+ // Event type filter
172
+ if (agent.events === 'all')
173
+ return true;
174
+ return agent.events.includes(event.type);
175
+ }
176
+ async deliverToAgent(agent, event) {
177
+ const message = formatEvent(event, agent, this.config.g2eApiUrl);
178
+ try {
179
+ await this.sink.deliver(agent, message, event);
180
+ this.stats.eventsDelivered++;
181
+ log('debug', `Delivered ${event.type} to ${agent.name}`);
182
+ }
183
+ catch (err) {
184
+ this.stats.deliveryErrors++;
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ log('error', `Failed to deliver ${event.type} to ${agent.name}: ${msg}`);
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * Parse SSE frames from a text buffer.
192
+ * SSE spec: frames are separated by double newlines.
193
+ * Each line is "field: value" or just "field".
194
+ */
195
+ function extractSSEFrames(buffer) {
196
+ const events = [];
197
+ // Split on double-newline boundaries (SSE frame separator)
198
+ const parts = buffer.split('\n\n');
199
+ // Last part might be incomplete — keep as remaining
200
+ const remaining = parts.pop() ?? '';
201
+ for (const part of parts) {
202
+ if (!part.trim())
203
+ continue;
204
+ const frame = {};
205
+ const dataLines = [];
206
+ for (const line of part.split('\n')) {
207
+ if (line.startsWith(':'))
208
+ continue; // Comment
209
+ const colonIdx = line.indexOf(':');
210
+ if (colonIdx === -1)
211
+ continue;
212
+ const field = line.slice(0, colonIdx).trim();
213
+ const value = line.slice(colonIdx + 1).trimStart();
214
+ switch (field) {
215
+ case 'event':
216
+ frame.event = value;
217
+ break;
218
+ case 'data':
219
+ dataLines.push(value);
220
+ break;
221
+ case 'id':
222
+ frame.id = value;
223
+ break;
224
+ case 'retry':
225
+ frame.retry = parseInt(value, 10);
226
+ break;
227
+ }
228
+ }
229
+ if (dataLines.length > 0) {
230
+ frame.data = dataLines.join('\n');
231
+ }
232
+ events.push(frame);
233
+ }
234
+ return { events, remaining };
235
+ }
236
+ function sleep(ms) {
237
+ return new Promise(resolve => setTimeout(resolve, ms));
238
+ }
239
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Config file management for g2e-bridge
3
+ *
4
+ * Default location: ~/.g2e-bridge.json
5
+ * Override with: --config <path>
6
+ */
7
+ import type { BridgeConfig } from './types.js';
8
+ export declare function resolveConfigPath(override?: string): string;
9
+ export declare function loadConfig(configPath: string): BridgeConfig;
10
+ export declare function saveConfig(configPath: string, config: BridgeConfig): void;
11
+ export declare function addAgent(configPath: string, agent: {
12
+ name: string;
13
+ apiKey: string;
14
+ sessionKey?: string;
15
+ events?: string[];
16
+ }): void;
17
+ export declare function removeAgent(configPath: string, name: string): boolean;
package/dist/config.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Config file management for g2e-bridge
3
+ *
4
+ * Default location: ~/.g2e-bridge.json
5
+ * Override with: --config <path>
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import os from 'node:os';
10
+ import { log } from './log.js';
11
+ import { DEFAULT_CONFIG, ALL_EVENT_TYPES } from './types.js';
12
+ const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.g2e-bridge.json');
13
+ export function resolveConfigPath(override) {
14
+ return override ?? DEFAULT_CONFIG_PATH;
15
+ }
16
+ export function loadConfig(configPath) {
17
+ if (!fs.existsSync(configPath)) {
18
+ log('warn', `Config file not found: ${configPath}. Using defaults with no agents.`);
19
+ return { ...DEFAULT_CONFIG, agents: [] };
20
+ }
21
+ const raw = fs.readFileSync(configPath, 'utf-8');
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(raw);
25
+ }
26
+ catch {
27
+ throw new Error(`Invalid JSON in config file: ${configPath}`);
28
+ }
29
+ return {
30
+ g2eApiUrl: parsed.g2eApiUrl ?? DEFAULT_CONFIG.g2eApiUrl,
31
+ agents: parsed.agents ?? [],
32
+ sink: parsed.sink ?? DEFAULT_CONFIG.sink,
33
+ reconnectMs: parsed.reconnectMs ?? DEFAULT_CONFIG.reconnectMs,
34
+ maxReconnectMs: parsed.maxReconnectMs ?? DEFAULT_CONFIG.maxReconnectMs,
35
+ };
36
+ }
37
+ export function saveConfig(configPath, config) {
38
+ const fileContent = {
39
+ g2eApiUrl: config.g2eApiUrl,
40
+ agents: config.agents,
41
+ sink: config.sink,
42
+ reconnectMs: config.reconnectMs,
43
+ maxReconnectMs: config.maxReconnectMs,
44
+ };
45
+ fs.writeFileSync(configPath, JSON.stringify(fileContent, null, 2) + '\n', 'utf-8');
46
+ }
47
+ export function addAgent(configPath, agent) {
48
+ const config = loadConfig(configPath);
49
+ const existing = config.agents.find(a => a.name === agent.name);
50
+ if (existing) {
51
+ throw new Error(`Agent "${agent.name}" already exists. Remove it first with: g2e-bridge remove --name ${agent.name}`);
52
+ }
53
+ // Validate event names
54
+ const events = agent.events
55
+ ? validateEvents(agent.events)
56
+ : 'all';
57
+ config.agents.push({
58
+ name: agent.name,
59
+ apiKey: agent.apiKey,
60
+ sessionKey: agent.sessionKey,
61
+ events,
62
+ });
63
+ saveConfig(configPath, config);
64
+ }
65
+ export function removeAgent(configPath, name) {
66
+ const config = loadConfig(configPath);
67
+ const before = config.agents.length;
68
+ config.agents = config.agents.filter(a => a.name !== name);
69
+ if (config.agents.length === before)
70
+ return false;
71
+ saveConfig(configPath, config);
72
+ return true;
73
+ }
74
+ function validateEvents(events) {
75
+ const valid = [];
76
+ for (const e of events) {
77
+ if (ALL_EVENT_TYPES.includes(e)) {
78
+ valid.push(e);
79
+ }
80
+ else {
81
+ throw new Error(`Unknown event type: "${e}". Valid types: ${ALL_EVENT_TYPES.join(', ')}`);
82
+ }
83
+ }
84
+ return valid;
85
+ }
86
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Event Formatter
3
+ *
4
+ * Transforms raw SSE events into human-readable agent-friendly messages.
5
+ * Each message includes the event type, relevant data summary, and
6
+ * actionable instructions (API calls the agent can make).
7
+ */
8
+ import type { SSEEvent, AgentConfig } from './types.js';
9
+ /**
10
+ * Format an SSE event into a message suitable for injection into an agent session.
11
+ * The message is plain-text with markdown-like formatting that most LLM agents handle well.
12
+ */
13
+ export declare function formatEvent(event: SSEEvent, agent: AgentConfig, apiUrl: string): string;