@poncho-ai/messaging 0.2.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/.turbo/turbo-build.log +14 -0
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +290 -0
- package/package.json +42 -0
- package/src/adapters/slack/index.ts +214 -0
- package/src/adapters/slack/utils.ts +107 -0
- package/src/adapters/slack/verify.ts +32 -0
- package/src/bridge.ts +88 -0
- package/src/index.ts +14 -0
- package/src/types.ts +98 -0
- package/test/adapters/slack.test.ts +241 -0
- package/test/bridge.test.ts +165 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { AgentBridge } from "../src/bridge.js";
|
|
3
|
+
import type {
|
|
4
|
+
AgentRunner,
|
|
5
|
+
IncomingMessage,
|
|
6
|
+
IncomingMessageHandler,
|
|
7
|
+
MessagingAdapter,
|
|
8
|
+
RouteRegistrar,
|
|
9
|
+
ThreadRef,
|
|
10
|
+
} from "../src/types.js";
|
|
11
|
+
|
|
12
|
+
const makeAdapter = (): MessagingAdapter & {
|
|
13
|
+
_handler: IncomingMessageHandler | undefined;
|
|
14
|
+
_replies: Array<{ ref: ThreadRef; content: string }>;
|
|
15
|
+
_processing: ThreadRef[];
|
|
16
|
+
} => ({
|
|
17
|
+
platform: "test",
|
|
18
|
+
_handler: undefined,
|
|
19
|
+
_replies: [],
|
|
20
|
+
_processing: [],
|
|
21
|
+
registerRoutes(_router: RouteRegistrar) {},
|
|
22
|
+
async initialize() {},
|
|
23
|
+
onMessage(handler) {
|
|
24
|
+
this._handler = handler;
|
|
25
|
+
},
|
|
26
|
+
async sendReply(ref, content) {
|
|
27
|
+
this._replies.push({ ref, content });
|
|
28
|
+
},
|
|
29
|
+
async indicateProcessing(ref) {
|
|
30
|
+
this._processing.push(ref);
|
|
31
|
+
return async () => {
|
|
32
|
+
const idx = this._processing.indexOf(ref);
|
|
33
|
+
if (idx >= 0) this._processing.splice(idx, 1);
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const makeRunner = (
|
|
39
|
+
response = "Hello from agent",
|
|
40
|
+
): AgentRunner & {
|
|
41
|
+
_conversations: Map<string, { messages: Array<{ role: string; content: string }> }>;
|
|
42
|
+
_runs: Array<{ id: string; task: string }>;
|
|
43
|
+
} => {
|
|
44
|
+
const conversations = new Map<string, { messages: Array<{ role: string; content: string }> }>();
|
|
45
|
+
const runs: Array<{ id: string; task: string }> = [];
|
|
46
|
+
return {
|
|
47
|
+
_conversations: conversations,
|
|
48
|
+
_runs: runs,
|
|
49
|
+
async getOrCreateConversation(id, _meta) {
|
|
50
|
+
if (!conversations.has(id)) {
|
|
51
|
+
conversations.set(id, { messages: [] });
|
|
52
|
+
}
|
|
53
|
+
return conversations.get(id)!;
|
|
54
|
+
},
|
|
55
|
+
async run(id, input) {
|
|
56
|
+
runs.push({ id, task: input.task });
|
|
57
|
+
return { response };
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const sampleMessage = (overrides?: Partial<IncomingMessage>): IncomingMessage => ({
|
|
63
|
+
text: "What is 2+2?",
|
|
64
|
+
threadRef: { platformThreadId: "ts_123", channelId: "C001" },
|
|
65
|
+
sender: { id: "U123", name: "alice" },
|
|
66
|
+
platform: "test",
|
|
67
|
+
raw: {},
|
|
68
|
+
...overrides,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("AgentBridge", () => {
|
|
72
|
+
it("derives deterministic conversation IDs from ThreadRef", async () => {
|
|
73
|
+
const adapter = makeAdapter();
|
|
74
|
+
const runner = makeRunner();
|
|
75
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
76
|
+
await bridge.start();
|
|
77
|
+
|
|
78
|
+
await adapter._handler!(sampleMessage());
|
|
79
|
+
|
|
80
|
+
expect(runner._runs).toHaveLength(1);
|
|
81
|
+
expect(runner._runs[0]!.id).toBe("test:C001:ts_123");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("sends the agent response back via the adapter", async () => {
|
|
85
|
+
const adapter = makeAdapter();
|
|
86
|
+
const runner = makeRunner("The answer is 4.");
|
|
87
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
88
|
+
await bridge.start();
|
|
89
|
+
|
|
90
|
+
await adapter._handler!(sampleMessage());
|
|
91
|
+
|
|
92
|
+
expect(adapter._replies).toHaveLength(1);
|
|
93
|
+
expect(adapter._replies[0]!.content).toBe("The answer is 4.");
|
|
94
|
+
expect(adapter._replies[0]!.ref.channelId).toBe("C001");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("cleans up the processing indicator after success", async () => {
|
|
98
|
+
const adapter = makeAdapter();
|
|
99
|
+
const runner = makeRunner();
|
|
100
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
101
|
+
await bridge.start();
|
|
102
|
+
|
|
103
|
+
await adapter._handler!(sampleMessage());
|
|
104
|
+
|
|
105
|
+
expect(adapter._processing).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("posts an error message and cleans up on runner failure", async () => {
|
|
109
|
+
const adapter = makeAdapter();
|
|
110
|
+
const runner = makeRunner();
|
|
111
|
+
runner.run = async () => {
|
|
112
|
+
throw new Error("Model overloaded");
|
|
113
|
+
};
|
|
114
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
115
|
+
await bridge.start();
|
|
116
|
+
|
|
117
|
+
await adapter._handler!(sampleMessage());
|
|
118
|
+
|
|
119
|
+
expect(adapter._replies).toHaveLength(1);
|
|
120
|
+
expect(adapter._replies[0]!.content).toContain("Model overloaded");
|
|
121
|
+
expect(adapter._processing).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("uses the same conversation ID for repeated messages in the same thread", async () => {
|
|
125
|
+
const adapter = makeAdapter();
|
|
126
|
+
const runner = makeRunner();
|
|
127
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
128
|
+
await bridge.start();
|
|
129
|
+
|
|
130
|
+
await adapter._handler!(sampleMessage());
|
|
131
|
+
await adapter._handler!(sampleMessage({ text: "followup" }));
|
|
132
|
+
|
|
133
|
+
expect(runner._runs).toHaveLength(2);
|
|
134
|
+
expect(runner._runs[0]!.id).toBe(runner._runs[1]!.id);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("creates different conversation IDs for different threads", async () => {
|
|
138
|
+
const adapter = makeAdapter();
|
|
139
|
+
const runner = makeRunner();
|
|
140
|
+
const bridge = new AgentBridge({ adapter, runner });
|
|
141
|
+
await bridge.start();
|
|
142
|
+
|
|
143
|
+
await adapter._handler!(sampleMessage());
|
|
144
|
+
await adapter._handler!(
|
|
145
|
+
sampleMessage({
|
|
146
|
+
threadRef: { platformThreadId: "ts_456", channelId: "C002" },
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(runner._runs[0]!.id).not.toBe(runner._runs[1]!.id);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("calls waitUntil for each message processed via onMessage", async () => {
|
|
154
|
+
const adapter = makeAdapter();
|
|
155
|
+
const runner = makeRunner();
|
|
156
|
+
const waitUntil = vi.fn();
|
|
157
|
+
const bridge = new AgentBridge({ adapter, runner, waitUntil });
|
|
158
|
+
await bridge.start();
|
|
159
|
+
|
|
160
|
+
await adapter._handler!(sampleMessage());
|
|
161
|
+
|
|
162
|
+
expect(waitUntil).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(waitUntil.mock.calls[0]![0]).toBeInstanceOf(Promise);
|
|
164
|
+
});
|
|
165
|
+
});
|