@elizaos/plugin-bluebubbles 2.0.0-alpha.3 → 2.0.0-alpha.4
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/dist/actions/index.d.ts +6 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/{src/actions/index.ts → dist/actions/index.js} +1 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/sendMessage.d.ts +6 -0
- package/dist/actions/sendMessage.d.ts.map +1 -0
- package/dist/actions/sendMessage.js +119 -0
- package/dist/actions/sendMessage.js.map +1 -0
- package/dist/actions/sendReaction.d.ts +6 -0
- package/dist/actions/sendReaction.d.ts.map +1 -0
- package/dist/actions/sendReaction.js +139 -0
- package/dist/actions/sendReaction.js.map +1 -0
- package/dist/client.d.ts +72 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +272 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.d.ts.map +1 -0
- package/{src/constants.ts → dist/constants.js} +18 -22
- package/dist/constants.js.map +1 -0
- package/dist/environment.d.ts +44 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +98 -0
- package/dist/environment.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/chatContext.d.ts +6 -0
- package/dist/providers/chatContext.d.ts.map +1 -0
- package/dist/providers/chatContext.js +80 -0
- package/dist/providers/chatContext.js.map +1 -0
- package/dist/providers/chatState.d.ts +6 -0
- package/dist/providers/chatState.d.ts.map +1 -0
- package/dist/providers/chatState.js +64 -0
- package/dist/providers/chatState.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/{src/providers/index.ts → dist/providers/index.js} +2 -1
- package/dist/providers/index.js.map +1 -0
- package/dist/service.d.ts +87 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +361 -0
- package/dist/service.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +18 -3
- package/__tests__/integration.test.ts +0 -260
- package/build.ts +0 -16
- package/src/actions/sendMessage.ts +0 -175
- package/src/actions/sendReaction.ts +0 -186
- package/src/client.ts +0 -389
- package/src/environment.ts +0 -120
- package/src/index.ts +0 -68
- package/src/providers/chatContext.ts +0 -105
- package/src/providers/chatState.ts +0 -90
- package/src/service.ts +0 -502
- package/src/types.ts +0 -165
- package/tsconfig.json +0 -22
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import blueBubblesPlugin, {
|
|
4
|
-
BLUEBUBBLES_SERVICE_NAME,
|
|
5
|
-
BlueBubblesService,
|
|
6
|
-
chatContextProvider,
|
|
7
|
-
sendMessageAction,
|
|
8
|
-
sendReactionAction,
|
|
9
|
-
} from "../src/index";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
normalizeHandle,
|
|
13
|
-
isHandleAllowed,
|
|
14
|
-
validateConfig,
|
|
15
|
-
} from "../src/environment";
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
DEFAULT_WEBHOOK_PATH,
|
|
19
|
-
API_ENDPOINTS,
|
|
20
|
-
DM_POLICY_OPEN,
|
|
21
|
-
DM_POLICY_DISABLED,
|
|
22
|
-
DM_POLICY_PAIRING,
|
|
23
|
-
DM_POLICY_ALLOWLIST,
|
|
24
|
-
GROUP_POLICY_OPEN,
|
|
25
|
-
GROUP_POLICY_ALLOWLIST,
|
|
26
|
-
GROUP_POLICY_DISABLED,
|
|
27
|
-
} from "../src/constants";
|
|
28
|
-
|
|
29
|
-
// ----------------------------------------------------------------
|
|
30
|
-
// Plugin exports
|
|
31
|
-
// ----------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
describe("BlueBubbles plugin exports", () => {
|
|
34
|
-
it("exports plugin metadata", () => {
|
|
35
|
-
expect(blueBubblesPlugin.name).toBe(BLUEBUBBLES_SERVICE_NAME);
|
|
36
|
-
expect(blueBubblesPlugin.description).toContain("BlueBubbles");
|
|
37
|
-
expect(Array.isArray(blueBubblesPlugin.actions)).toBe(true);
|
|
38
|
-
expect(Array.isArray(blueBubblesPlugin.providers)).toBe(true);
|
|
39
|
-
expect(Array.isArray(blueBubblesPlugin.services)).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("exports actions, providers, and service", () => {
|
|
43
|
-
expect(sendMessageAction).toBeDefined();
|
|
44
|
-
expect(sendReactionAction).toBeDefined();
|
|
45
|
-
expect(chatContextProvider).toBeDefined();
|
|
46
|
-
expect(BlueBubblesService).toBeDefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("registers exactly 2 actions", () => {
|
|
50
|
-
expect(blueBubblesPlugin.actions).toHaveLength(2);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("registers exactly 1 provider", () => {
|
|
54
|
-
expect(blueBubblesPlugin.providers).toHaveLength(1);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ----------------------------------------------------------------
|
|
59
|
-
// normalizeHandle
|
|
60
|
-
// ----------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
describe("normalizeHandle", () => {
|
|
63
|
-
it("normalizes a formatted US phone number", () => {
|
|
64
|
-
expect(normalizeHandle("+1 (555) 123-4567")).toBe("+15551234567");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("normalizes a phone number without plus prefix", () => {
|
|
68
|
-
expect(normalizeHandle("555-123-4567")).toBe("+5551234567");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("normalizes an international phone number", () => {
|
|
72
|
-
expect(normalizeHandle("+44 7700 900000")).toBe("+447700900000");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("lowercases an email address", () => {
|
|
76
|
-
expect(normalizeHandle("User@Example.COM")).toBe("user@example.com");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("trims whitespace from an email", () => {
|
|
80
|
-
expect(normalizeHandle(" test@test.com ")).toBe("test@test.com");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("handles short digit strings without adding plus", () => {
|
|
84
|
-
expect(normalizeHandle("12345")).toBe("12345");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("handles a phone number with dots", () => {
|
|
88
|
-
expect(normalizeHandle("+1.555.123.4567")).toBe("+15551234567");
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ----------------------------------------------------------------
|
|
93
|
-
// isHandleAllowed
|
|
94
|
-
// ----------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
describe("isHandleAllowed", () => {
|
|
97
|
-
it("open policy allows any handle", () => {
|
|
98
|
-
expect(isHandleAllowed("anyone@example.com", [], DM_POLICY_OPEN)).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("disabled policy denies all handles", () => {
|
|
102
|
-
expect(isHandleAllowed("anyone@example.com", [], DM_POLICY_DISABLED)).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("pairing with empty allowlist allows first contact", () => {
|
|
106
|
-
expect(isHandleAllowed("first@contact.com", [], DM_POLICY_PAIRING)).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("pairing with non-empty allowlist only allows listed handles", () => {
|
|
110
|
-
expect(isHandleAllowed("+15551234567", ["+15551234567"], DM_POLICY_PAIRING)).toBe(true);
|
|
111
|
-
expect(isHandleAllowed("+15559999999", ["+15551234567"], DM_POLICY_PAIRING)).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("allowlist matches normalized handles", () => {
|
|
115
|
-
const allowList = ["+15551234567"];
|
|
116
|
-
expect(isHandleAllowed("+1 (555) 123-4567", allowList, DM_POLICY_ALLOWLIST)).toBe(true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("allowlist rejects non-matching handles", () => {
|
|
120
|
-
const allowList = ["+15551234567"];
|
|
121
|
-
expect(isHandleAllowed("+15559876543", allowList, DM_POLICY_ALLOWLIST)).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("group open policy allows all", () => {
|
|
125
|
-
expect(isHandleAllowed("anyone", [], GROUP_POLICY_OPEN)).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("group disabled policy denies all", () => {
|
|
129
|
-
expect(isHandleAllowed("anyone", [], GROUP_POLICY_DISABLED)).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("group allowlist matches normalized handles", () => {
|
|
133
|
-
expect(isHandleAllowed("+1 555 123 4567", ["+15551234567"], GROUP_POLICY_ALLOWLIST)).toBe(true);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ----------------------------------------------------------------
|
|
138
|
-
// validateConfig
|
|
139
|
-
// ----------------------------------------------------------------
|
|
140
|
-
|
|
141
|
-
describe("validateConfig", () => {
|
|
142
|
-
it("accepts a valid config", () => {
|
|
143
|
-
const config = validateConfig({
|
|
144
|
-
serverUrl: "http://localhost:1234",
|
|
145
|
-
password: "secret",
|
|
146
|
-
});
|
|
147
|
-
expect(config.serverUrl).toBe("http://localhost:1234");
|
|
148
|
-
expect(config.password).toBe("secret");
|
|
149
|
-
expect(config.webhookPath).toBe(DEFAULT_WEBHOOK_PATH);
|
|
150
|
-
expect(config.dmPolicy).toBe("pairing");
|
|
151
|
-
expect(config.groupPolicy).toBe("allowlist");
|
|
152
|
-
expect(config.sendReadReceipts).toBe(true);
|
|
153
|
-
expect(config.enabled).toBe(true);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("rejects missing server URL", () => {
|
|
157
|
-
expect(() =>
|
|
158
|
-
validateConfig({ serverUrl: "", password: "secret" })
|
|
159
|
-
).toThrow();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("rejects missing password", () => {
|
|
163
|
-
expect(() =>
|
|
164
|
-
validateConfig({ serverUrl: "http://localhost:1234", password: "" })
|
|
165
|
-
).toThrow();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("rejects an invalid URL", () => {
|
|
169
|
-
expect(() =>
|
|
170
|
-
validateConfig({ serverUrl: "not-a-url", password: "secret" })
|
|
171
|
-
).toThrow();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("preserves custom webhook path", () => {
|
|
175
|
-
const config = validateConfig({
|
|
176
|
-
serverUrl: "http://localhost:1234",
|
|
177
|
-
password: "secret",
|
|
178
|
-
webhookPath: "/custom/webhook",
|
|
179
|
-
});
|
|
180
|
-
expect(config.webhookPath).toBe("/custom/webhook");
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// ----------------------------------------------------------------
|
|
185
|
-
// Action definitions
|
|
186
|
-
// ----------------------------------------------------------------
|
|
187
|
-
|
|
188
|
-
describe("sendMessageAction", () => {
|
|
189
|
-
it("has the correct name", () => {
|
|
190
|
-
expect(sendMessageAction.name).toBe("SEND_BLUEBUBBLES_MESSAGE");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("has a non-empty description", () => {
|
|
194
|
-
expect(sendMessageAction.description).toBeTruthy();
|
|
195
|
-
expect(sendMessageAction.description!.length).toBeGreaterThan(10);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("has similes", () => {
|
|
199
|
-
expect(Array.isArray(sendMessageAction.similes)).toBe(true);
|
|
200
|
-
expect(sendMessageAction.similes!.length).toBeGreaterThan(0);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("has at least one example", () => {
|
|
204
|
-
expect(Array.isArray(sendMessageAction.examples)).toBe(true);
|
|
205
|
-
expect(sendMessageAction.examples!.length).toBeGreaterThan(0);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("validate rejects when service is missing", async () => {
|
|
209
|
-
const mockRuntime = {
|
|
210
|
-
getService: vi.fn().mockReturnValue(null),
|
|
211
|
-
} as any;
|
|
212
|
-
const mockMessage = { content: { source: "bluebubbles" } } as any;
|
|
213
|
-
const result = await sendMessageAction.validate!(mockRuntime, mockMessage);
|
|
214
|
-
expect(result).toBe(false);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe("sendReactionAction", () => {
|
|
219
|
-
it("has the correct name", () => {
|
|
220
|
-
expect(sendReactionAction.name).toBe("BLUEBUBBLES_SEND_REACTION");
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("has a non-empty description", () => {
|
|
224
|
-
expect(sendReactionAction.description).toBeTruthy();
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("has similes including BLUEBUBBLES_REACT", () => {
|
|
228
|
-
expect(sendReactionAction.similes).toContain("BLUEBUBBLES_REACT");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("validate rejects non-bluebubbles sources", async () => {
|
|
232
|
-
const mockRuntime = {} as any;
|
|
233
|
-
const mockMessage = { content: { source: "discord" } } as any;
|
|
234
|
-
const result = await sendReactionAction.validate!(mockRuntime, mockMessage);
|
|
235
|
-
expect(result).toBe(false);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("validate accepts bluebubbles source", async () => {
|
|
239
|
-
const mockRuntime = {} as any;
|
|
240
|
-
const mockMessage = { content: { source: "bluebubbles" } } as any;
|
|
241
|
-
const result = await sendReactionAction.validate!(mockRuntime, mockMessage);
|
|
242
|
-
expect(result).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// ----------------------------------------------------------------
|
|
247
|
-
// Constants
|
|
248
|
-
// ----------------------------------------------------------------
|
|
249
|
-
|
|
250
|
-
describe("constants", () => {
|
|
251
|
-
it("has expected API endpoints", () => {
|
|
252
|
-
expect(API_ENDPOINTS.SEND_MESSAGE).toBeDefined();
|
|
253
|
-
expect(API_ENDPOINTS.REACT).toBeDefined();
|
|
254
|
-
expect(API_ENDPOINTS.SERVER_INFO).toBeDefined();
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("service name is bluebubbles", () => {
|
|
258
|
-
expect(BLUEBUBBLES_SERVICE_NAME).toBe("bluebubbles");
|
|
259
|
-
});
|
|
260
|
-
});
|
package/build.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { rmSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
const distDir = join(import.meta.dirname, "dist");
|
|
6
|
-
|
|
7
|
-
// Clean
|
|
8
|
-
rmSync(distDir, { recursive: true, force: true });
|
|
9
|
-
|
|
10
|
-
// Build
|
|
11
|
-
execSync("npx tsc -p tsconfig.json", {
|
|
12
|
-
cwd: import.meta.dirname,
|
|
13
|
-
stdio: "inherit",
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
console.log("Build complete: plugin-bluebubbles");
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send message action for BlueBubbles
|
|
3
|
-
*/
|
|
4
|
-
import {
|
|
5
|
-
type Action,
|
|
6
|
-
type ActionExample,
|
|
7
|
-
type ActionResult,
|
|
8
|
-
type Content,
|
|
9
|
-
composePromptFromState,
|
|
10
|
-
type HandlerCallback,
|
|
11
|
-
type IAgentRuntime,
|
|
12
|
-
logger,
|
|
13
|
-
type Memory,
|
|
14
|
-
ModelType,
|
|
15
|
-
type State,
|
|
16
|
-
} from "@elizaos/core";
|
|
17
|
-
import { BLUEBUBBLES_SERVICE_NAME } from "../constants";
|
|
18
|
-
import type { BlueBubblesService } from "../service";
|
|
19
|
-
|
|
20
|
-
const sendMessageTemplate = `
|
|
21
|
-
# Task: Generate a response to send via iMessage (BlueBubbles)
|
|
22
|
-
{{recentMessages}}
|
|
23
|
-
|
|
24
|
-
# Instructions: Write a response to send to the user via iMessage. Be conversational and friendly.
|
|
25
|
-
Your response should be appropriate for iMessage - keep it relatively concise but engaging.
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const examples: ActionExample[][] = [
|
|
29
|
-
[
|
|
30
|
-
{
|
|
31
|
-
name: "{{user1}}",
|
|
32
|
-
content: {
|
|
33
|
-
text: "Can you send a message to John saying I'll be late?",
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: "{{agentName}}",
|
|
38
|
-
content: {
|
|
39
|
-
text: "I'll send that message to John for you.",
|
|
40
|
-
action: "SEND_BLUEBUBBLES_MESSAGE",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
[
|
|
45
|
-
{
|
|
46
|
-
name: "{{user1}}",
|
|
47
|
-
content: {
|
|
48
|
-
text: "Reply to this iMessage for me",
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: "{{agentName}}",
|
|
53
|
-
content: {
|
|
54
|
-
text: "I'll compose and send a reply for you.",
|
|
55
|
-
action: "SEND_BLUEBUBBLES_MESSAGE",
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
export const sendMessageAction: Action = {
|
|
62
|
-
name: "SEND_BLUEBUBBLES_MESSAGE",
|
|
63
|
-
description: "Send a message via iMessage through BlueBubbles",
|
|
64
|
-
similes: [
|
|
65
|
-
"SEND_IMESSAGE",
|
|
66
|
-
"TEXT_MESSAGE",
|
|
67
|
-
"IMESSAGE_REPLY",
|
|
68
|
-
"BLUEBUBBLES_SEND",
|
|
69
|
-
"APPLE_MESSAGE",
|
|
70
|
-
],
|
|
71
|
-
examples,
|
|
72
|
-
|
|
73
|
-
validate: async (
|
|
74
|
-
runtime: IAgentRuntime,
|
|
75
|
-
_message: Memory,
|
|
76
|
-
): Promise<boolean> => {
|
|
77
|
-
const service = runtime.getService<BlueBubblesService>(
|
|
78
|
-
BLUEBUBBLES_SERVICE_NAME,
|
|
79
|
-
);
|
|
80
|
-
return service?.getIsRunning() ?? false;
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
handler: async (
|
|
84
|
-
runtime: IAgentRuntime,
|
|
85
|
-
message: Memory,
|
|
86
|
-
state: State | undefined,
|
|
87
|
-
_options: Record<string, unknown> | undefined,
|
|
88
|
-
callback?: HandlerCallback,
|
|
89
|
-
): Promise<ActionResult> => {
|
|
90
|
-
const service = runtime.getService<BlueBubblesService>(
|
|
91
|
-
BLUEBUBBLES_SERVICE_NAME,
|
|
92
|
-
);
|
|
93
|
-
const currentState = state ?? (await runtime.composeState(message));
|
|
94
|
-
|
|
95
|
-
if (!service || !service.getIsRunning()) {
|
|
96
|
-
logger.error("BlueBubbles service is not available");
|
|
97
|
-
if (callback) {
|
|
98
|
-
await callback({
|
|
99
|
-
text: "Sorry, the iMessage service is currently unavailable.",
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return { success: false, error: "BlueBubbles service not available" };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// Get the room to find the target
|
|
107
|
-
const room = await runtime.getRoom(message.roomId);
|
|
108
|
-
if (!room?.channelId) {
|
|
109
|
-
logger.error("No channel ID found for room");
|
|
110
|
-
if (callback) {
|
|
111
|
-
await callback({
|
|
112
|
-
text: "Unable to determine the message recipient.",
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
return { success: false, error: "No channel ID" };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Generate response if state is available
|
|
119
|
-
const prompt = composePromptFromState({
|
|
120
|
-
state: currentState,
|
|
121
|
-
template: sendMessageTemplate,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
|
|
125
|
-
prompt,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const responseText =
|
|
129
|
-
typeof response === "string"
|
|
130
|
-
? response
|
|
131
|
-
: ((response as { text?: string }).text ?? "");
|
|
132
|
-
|
|
133
|
-
if (!responseText.trim()) {
|
|
134
|
-
logger.warn("Generated empty response, skipping send");
|
|
135
|
-
return { success: false, error: "Empty response generated" };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Send the message
|
|
139
|
-
const result = await service.sendMessage(
|
|
140
|
-
room.channelId,
|
|
141
|
-
responseText,
|
|
142
|
-
message.content.inReplyTo as string | undefined,
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
logger.info(`Sent BlueBubbles message: ${result.guid}`);
|
|
146
|
-
|
|
147
|
-
const content: Content = {
|
|
148
|
-
text: responseText,
|
|
149
|
-
source: "bluebubbles",
|
|
150
|
-
metadata: {
|
|
151
|
-
messageGuid: result.guid,
|
|
152
|
-
chatGuid: room.channelId,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
if (callback) {
|
|
157
|
-
await callback(content);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return { success: true, text: responseText };
|
|
161
|
-
} catch (error) {
|
|
162
|
-
const errorMessage =
|
|
163
|
-
error instanceof Error ? error.message : String(error);
|
|
164
|
-
logger.error(`Failed to send BlueBubbles message: ${errorMessage}`);
|
|
165
|
-
|
|
166
|
-
if (callback) {
|
|
167
|
-
await callback({
|
|
168
|
-
text: "Failed to send the iMessage. Please try again.",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return { success: false, error: errorMessage };
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Send reaction action for the BlueBubbles plugin.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Action, ActionResult, IAgentRuntime, Memory, State, HandlerCallback } from "@elizaos/core";
|
|
6
|
-
import {
|
|
7
|
-
composePromptFromState,
|
|
8
|
-
logger,
|
|
9
|
-
ModelType,
|
|
10
|
-
parseJSONObjectFromText,
|
|
11
|
-
} from "@elizaos/core";
|
|
12
|
-
import { BlueBubblesService } from "../service.js";
|
|
13
|
-
import { BLUEBUBBLES_SERVICE_NAME } from "../constants.js";
|
|
14
|
-
|
|
15
|
-
const SEND_REACTION_TEMPLATE = `# Task: Extract BlueBubbles reaction parameters
|
|
16
|
-
|
|
17
|
-
Based on the conversation, determine what reaction to add or remove.
|
|
18
|
-
|
|
19
|
-
Recent conversation:
|
|
20
|
-
{{recentMessages}}
|
|
21
|
-
|
|
22
|
-
Extract the following:
|
|
23
|
-
1. emoji: The emoji reaction to add (heart, thumbsup, thumbsdown, haha, exclamation, question, or any emoji)
|
|
24
|
-
2. messageId: The message ID to react to (or "last" for the last message)
|
|
25
|
-
3. remove: true to remove the reaction, false to add it
|
|
26
|
-
|
|
27
|
-
Respond with a JSON object:
|
|
28
|
-
\`\`\`json
|
|
29
|
-
{
|
|
30
|
-
"emoji": "❤️",
|
|
31
|
-
"messageId": "last",
|
|
32
|
-
"remove": false
|
|
33
|
-
}
|
|
34
|
-
\`\`\`
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
interface ReactionParams {
|
|
38
|
-
emoji: string;
|
|
39
|
-
messageId: string;
|
|
40
|
-
remove: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const sendReactionAction: Action = {
|
|
44
|
-
name: "BLUEBUBBLES_SEND_REACTION",
|
|
45
|
-
similes: ["BLUEBUBBLES_REACT", "BB_REACTION", "IMESSAGE_REACT"],
|
|
46
|
-
description: "Add or remove a reaction on a message via BlueBubbles",
|
|
47
|
-
|
|
48
|
-
validate: async (
|
|
49
|
-
runtime: IAgentRuntime,
|
|
50
|
-
message: Memory,
|
|
51
|
-
_state?: State
|
|
52
|
-
): Promise<boolean> => {
|
|
53
|
-
return message.content.source === "bluebubbles";
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
handler: async (
|
|
57
|
-
runtime: IAgentRuntime,
|
|
58
|
-
message: Memory,
|
|
59
|
-
state: State | undefined,
|
|
60
|
-
_options?: Record<string, unknown>,
|
|
61
|
-
callback?: HandlerCallback
|
|
62
|
-
): Promise<ActionResult> => {
|
|
63
|
-
const bbService = runtime.getService<BlueBubblesService>(BLUEBUBBLES_SERVICE_NAME);
|
|
64
|
-
const currentState = state ?? (await runtime.composeState(message));
|
|
65
|
-
|
|
66
|
-
if (!bbService || !bbService.isConnected()) {
|
|
67
|
-
if (callback) {
|
|
68
|
-
await callback({ text: "BlueBubbles service is not available.", source: "bluebubbles" });
|
|
69
|
-
}
|
|
70
|
-
return { success: false, error: "BlueBubbles service not available" };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Extract parameters using LLM
|
|
74
|
-
const prompt = await composePromptFromState({
|
|
75
|
-
template: SEND_REACTION_TEMPLATE,
|
|
76
|
-
state: currentState,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
let reactionInfo: ReactionParams | null = null;
|
|
80
|
-
|
|
81
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
82
|
-
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
83
|
-
prompt,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const parsed = parseJSONObjectFromText(response);
|
|
87
|
-
if (parsed?.emoji) {
|
|
88
|
-
reactionInfo = {
|
|
89
|
-
emoji: String(parsed.emoji),
|
|
90
|
-
messageId: String(parsed.messageId || "last"),
|
|
91
|
-
remove: Boolean(parsed.remove),
|
|
92
|
-
};
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!reactionInfo || !reactionInfo.emoji) {
|
|
98
|
-
if (callback) {
|
|
99
|
-
await callback({
|
|
100
|
-
text: "I couldn't understand the reaction. Please specify an emoji.",
|
|
101
|
-
source: "bluebubbles",
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return { success: false, error: "Could not extract reaction parameters" };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Get chat context
|
|
108
|
-
const stateData = (currentState.data || {}) as Record<string, unknown>;
|
|
109
|
-
const chatGuid = stateData.chatGuid as string;
|
|
110
|
-
let messageGuid = reactionInfo.messageId;
|
|
111
|
-
|
|
112
|
-
if (!chatGuid) {
|
|
113
|
-
if (callback) {
|
|
114
|
-
await callback({
|
|
115
|
-
text: "I couldn't determine the chat to react in.",
|
|
116
|
-
source: "bluebubbles",
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return { success: false, error: "Could not determine chat" };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// If "last", get the last message GUID from context
|
|
123
|
-
if (messageGuid === "last" || !messageGuid) {
|
|
124
|
-
messageGuid = stateData.lastMessageGuid as string;
|
|
125
|
-
if (!messageGuid) {
|
|
126
|
-
if (callback) {
|
|
127
|
-
await callback({
|
|
128
|
-
text: "I couldn't find the message to react to.",
|
|
129
|
-
source: "bluebubbles",
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return { success: false, error: "Could not find message to react to" };
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Send reaction - we only support adding reactions, not removing
|
|
137
|
-
// The BlueBubbles API handles remove through a negative reaction type internally
|
|
138
|
-
const reactionValue = reactionInfo.remove ? `-${reactionInfo.emoji}` : reactionInfo.emoji;
|
|
139
|
-
const result = await bbService.sendReaction(
|
|
140
|
-
chatGuid,
|
|
141
|
-
messageGuid,
|
|
142
|
-
reactionValue
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (!result.success) {
|
|
146
|
-
if (callback) {
|
|
147
|
-
await callback({
|
|
148
|
-
text: `Failed to ${reactionInfo.remove ? "remove" : "add"} reaction.`,
|
|
149
|
-
source: "bluebubbles",
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return { success: false, error: "Failed to send reaction" };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
logger.debug(
|
|
156
|
-
`${reactionInfo.remove ? "Removed" : "Added"} reaction ${reactionInfo.emoji} on ${messageGuid}`
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
if (callback) {
|
|
160
|
-
await callback({
|
|
161
|
-
text: reactionInfo.remove
|
|
162
|
-
? "Reaction removed."
|
|
163
|
-
: `Reacted with ${reactionInfo.emoji}.`,
|
|
164
|
-
source: message.content.source as string,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return { success: true };
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
examples: [
|
|
172
|
-
[
|
|
173
|
-
{
|
|
174
|
-
name: "{{user1}}",
|
|
175
|
-
content: { text: "React to that message with a heart" },
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: "{{agent}}",
|
|
179
|
-
content: {
|
|
180
|
-
text: "I'll add a heart reaction.",
|
|
181
|
-
actions: ["BLUEBUBBLES_SEND_REACTION"],
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
],
|
|
186
|
-
};
|