@friendlyrobot/discord-pi-agent 0.5.9 → 0.6.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 +31 -7
- package/dist/discord-gateway-client.d.ts +0 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -37
- package/dist/prompt-context.d.ts +15 -3
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/discord-gateway-client.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -23,6 +23,31 @@ Reusable Discord gateway bridge for persistent pi agent sessions — DM and foru
|
|
|
23
23
|
|
|
24
24
|
Any other text is sent to the active session (DM or thread).
|
|
25
25
|
|
|
26
|
+
## Prompt metadata
|
|
27
|
+
|
|
28
|
+
Every Discord prompt is wrapped with lightweight Discord context before `promptTransform` runs:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
<discord_message_context>
|
|
32
|
+
{
|
|
33
|
+
"scope": "thread",
|
|
34
|
+
"sent_at": "2026-05-07T04:31:00.000Z",
|
|
35
|
+
"sent_at_local": "Thu, 7 May 26, 14:31 AEST",
|
|
36
|
+
"message_id": "...",
|
|
37
|
+
"author_name": "Alice",
|
|
38
|
+
"author_id": "...",
|
|
39
|
+
"thread_title": "Bug report",
|
|
40
|
+
"thread_id": "...",
|
|
41
|
+
"forum_channel_id": "..."
|
|
42
|
+
}
|
|
43
|
+
</discord_message_context>
|
|
44
|
+
|
|
45
|
+
User message:
|
|
46
|
+
...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
DM prompts omit thread-only fields. `sent_at_local` uses `promptTimeZone` and `promptLocale`.
|
|
50
|
+
|
|
26
51
|
## Install
|
|
27
52
|
|
|
28
53
|
```bash
|
|
@@ -33,19 +58,14 @@ bun add @friendlyrobot/discord-pi-agent
|
|
|
33
58
|
|
|
34
59
|
```ts
|
|
35
60
|
import {
|
|
36
|
-
buildTimeContextPrompt,
|
|
37
61
|
loadDiscordGatewayConfigFromEnv,
|
|
38
62
|
startDiscordGateway,
|
|
39
63
|
} from "@friendlyrobot/discord-pi-agent";
|
|
40
64
|
|
|
41
65
|
const config = loadDiscordGatewayConfigFromEnv({
|
|
42
66
|
cwd: process.cwd(),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
timeZone: "Australia/Sydney",
|
|
46
|
-
locale: "en-AU",
|
|
47
|
-
});
|
|
48
|
-
},
|
|
67
|
+
promptTimeZone: "Australia/Sydney",
|
|
68
|
+
promptLocale: "en-AU",
|
|
49
69
|
// Enable forum channel support (omit for DM-only)
|
|
50
70
|
discordAllowedForumChannelIds: ["1498563501780897832"],
|
|
51
71
|
});
|
|
@@ -70,6 +90,8 @@ The initial post body becomes the first prompt. Sessions survive restarts.
|
|
|
70
90
|
- `modelProvider` default: `openrouter`
|
|
71
91
|
- `modelId` default: `anthropic/claude-3.5-haiku`
|
|
72
92
|
- `thinkingLevel` default: `medium` (values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`)
|
|
93
|
+
- `promptTimeZone` default: `UTC` — used for `sent_at_local` in Discord prompt metadata
|
|
94
|
+
- `promptLocale` default: `en-AU` — used for `sent_at_local` in Discord prompt metadata
|
|
73
95
|
- `promptTransform` default: identity
|
|
74
96
|
- `startupMessage` default: `Bot is online and ready.`
|
|
75
97
|
- `shutdownOnSignals` default: `true`
|
|
@@ -90,6 +112,8 @@ The initial post body becomes the first prompt. Sessions survive restarts.
|
|
|
90
112
|
- `PI_AGENT_DIR`
|
|
91
113
|
- `PI_MODEL_PROVIDER`
|
|
92
114
|
- `PI_MODEL_ID`
|
|
115
|
+
- `PI_PROMPT_TIME_ZONE`
|
|
116
|
+
- `PI_PROMPT_LOCALE`
|
|
93
117
|
- `DISCORD_STARTUP_MESSAGE`
|
|
94
118
|
- `DISCORD_FORUM_CHANNEL_IDS` — comma-separated forum channel IDs
|
|
95
119
|
- `DISCORD_ALLOWED_USER_IDS` — comma-separated allowed user IDs
|
|
@@ -8,8 +8,4 @@ export type GatewayAuthConfig = {
|
|
|
8
8
|
discordAllowedUserIds: string[];
|
|
9
9
|
startupMessage: string | false;
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
12
|
-
* Combine a forum thread title with the post body for the initial session prompt.
|
|
13
|
-
*/
|
|
14
|
-
export declare function buildThreadOpeningPrompt(threadName: string, content: string): string;
|
|
15
11
|
export declare function startGatewayClient(config: ResolvedDiscordPiBridgeConfig, agentService: AgentService, sessionRegistry: SessionRegistry, authConfig: GatewayAuthConfig): Promise<Client>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DiscordGateway, DiscordGatewayConfig, DiscordPiBridge, DiscordPiBridgeConfig } from "./types";
|
|
2
|
-
export {
|
|
2
|
+
export { buildDiscordMessageContextPrompt, formatDiscordPromptTime, type DiscordMessageContextPromptOptions, type DiscordPromptScope, type DiscordPromptTimeFormatOptions, } from "./prompt-context";
|
|
3
3
|
export { loadDiscordPiBridgeConfigFromEnv, loadDiscordGatewayConfigFromEnv, resolveConfig, } from "./config";
|
|
4
4
|
export type { AgentStatus, DiscordGateway, DiscordGatewayConfig, DiscordPiBridge, DiscordPiBridgeConfig, PromptTransform, ResolvedDiscordPiBridgeConfig, } from "./types";
|
|
5
5
|
/**
|
package/dist/index.js
CHANGED
|
@@ -452,6 +452,8 @@ function resolveConfig(config) {
|
|
|
452
452
|
modelProvider: config.modelProvider?.trim() || "openrouter",
|
|
453
453
|
modelId: config.modelId?.trim() || "anthropic/claude-3.5-haiku",
|
|
454
454
|
thinkingLevel: parseThinkingLevel(config.thinkingLevel) || "medium",
|
|
455
|
+
promptTimeZone: config.promptTimeZone?.trim() || "UTC",
|
|
456
|
+
promptLocale: config.promptLocale?.trim() || "en-AU",
|
|
455
457
|
promptTransform: config.promptTransform || identityPromptTransform,
|
|
456
458
|
startupMessage: config.startupMessage === undefined ? "Bot is online and ready." : config.startupMessage,
|
|
457
459
|
shutdownOnSignals: config.shutdownOnSignals ?? true
|
|
@@ -467,6 +469,8 @@ function loadDiscordPiBridgeConfigFromEnv(overrides = {}) {
|
|
|
467
469
|
modelProvider: overrides.modelProvider || process.env.PI_MODEL_PROVIDER,
|
|
468
470
|
modelId: overrides.modelId || process.env.PI_MODEL_ID,
|
|
469
471
|
thinkingLevel: parseThinkingLevel(overrides.thinkingLevel || process.env.PI_THINKING_LEVEL),
|
|
472
|
+
promptTimeZone: overrides.promptTimeZone || process.env.PI_PROMPT_TIME_ZONE,
|
|
473
|
+
promptLocale: overrides.promptLocale || process.env.PI_PROMPT_LOCALE,
|
|
470
474
|
promptTransform: overrides.promptTransform,
|
|
471
475
|
startupMessage: overrides.startupMessage ?? readStartupMessageFromEnv(),
|
|
472
476
|
shutdownOnSignals: overrides.shutdownOnSignals
|
|
@@ -802,11 +806,74 @@ function chunkMessage(text, maxChunkSize = SAFE_MESSAGE_LIMIT) {
|
|
|
802
806
|
return chunks.map((chunk) => chunk.slice(0, DISCORD_MESSAGE_LIMIT));
|
|
803
807
|
}
|
|
804
808
|
|
|
805
|
-
// src/
|
|
806
|
-
function
|
|
807
|
-
|
|
809
|
+
// src/prompt-context.ts
|
|
810
|
+
function buildDiscordMessageContextPrompt(userMessage, options) {
|
|
811
|
+
const contextEntries = [
|
|
812
|
+
["scope", options.scope],
|
|
813
|
+
["sent_at", options.sentAt],
|
|
814
|
+
["sent_at_local", options.sentAtLocal],
|
|
815
|
+
["message_id", options.messageId],
|
|
816
|
+
["author_name", normalizeContextValue(options.authorName)],
|
|
817
|
+
["author_id", options.authorId],
|
|
818
|
+
["thread_title", normalizeContextValue(options.threadTitle)],
|
|
819
|
+
["thread_id", options.threadId],
|
|
820
|
+
["forum_channel_id", options.forumChannelId ?? undefined]
|
|
821
|
+
].filter((entry) => {
|
|
822
|
+
return typeof entry[1] === "string" && entry[1].trim().length > 0;
|
|
823
|
+
});
|
|
824
|
+
const contextJson = JSON.stringify(Object.fromEntries(contextEntries), null, 2);
|
|
825
|
+
return [
|
|
826
|
+
"<discord_message_context>",
|
|
827
|
+
contextJson,
|
|
828
|
+
"</discord_message_context>",
|
|
829
|
+
"",
|
|
830
|
+
"User message:",
|
|
831
|
+
userMessage.trim()
|
|
832
|
+
].join(`
|
|
833
|
+
`);
|
|
834
|
+
}
|
|
835
|
+
function formatDiscordPromptTime(date, options = {}) {
|
|
836
|
+
const timeZone = options.timeZone || "UTC";
|
|
837
|
+
const locale = options.locale || "en-AU";
|
|
838
|
+
return new Intl.DateTimeFormat(locale, {
|
|
839
|
+
timeZone,
|
|
840
|
+
weekday: "short",
|
|
841
|
+
day: "numeric",
|
|
842
|
+
month: "short",
|
|
843
|
+
year: "2-digit",
|
|
844
|
+
hour: "2-digit",
|
|
845
|
+
minute: "2-digit",
|
|
846
|
+
hour12: false,
|
|
847
|
+
timeZoneName: "short"
|
|
848
|
+
}).format(date);
|
|
849
|
+
}
|
|
850
|
+
function normalizeContextValue(value) {
|
|
851
|
+
if (value === undefined) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
return value.replace(/\s+/g, " ").trim();
|
|
855
|
+
}
|
|
808
856
|
|
|
809
|
-
|
|
857
|
+
// src/discord-gateway-client.ts
|
|
858
|
+
function getAuthorDisplayName(message) {
|
|
859
|
+
return message.member?.displayName || message.author.globalName || message.author.username;
|
|
860
|
+
}
|
|
861
|
+
function buildDiscordPromptContent(message, scope, content, config) {
|
|
862
|
+
const isThread = scope.startsWith("thread:") && message.channel.isThread();
|
|
863
|
+
return buildDiscordMessageContextPrompt(content, {
|
|
864
|
+
scope: scope === "dm" ? "dm" : "thread",
|
|
865
|
+
sentAt: message.createdAt.toISOString(),
|
|
866
|
+
sentAtLocal: formatDiscordPromptTime(message.createdAt, {
|
|
867
|
+
timeZone: config.promptTimeZone,
|
|
868
|
+
locale: config.promptLocale
|
|
869
|
+
}),
|
|
870
|
+
messageId: message.id,
|
|
871
|
+
authorId: message.author.id,
|
|
872
|
+
authorName: getAuthorDisplayName(message),
|
|
873
|
+
threadId: isThread ? message.channel.id : undefined,
|
|
874
|
+
threadTitle: isThread ? message.channel.name : undefined,
|
|
875
|
+
forumChannelId: isThread ? message.channel.parentId : undefined
|
|
876
|
+
});
|
|
810
877
|
}
|
|
811
878
|
function resolveScope(message) {
|
|
812
879
|
if (message.channel.type === ChannelType.DM) {
|
|
@@ -969,16 +1036,11 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
969
1036
|
}
|
|
970
1037
|
const { entry, created } = await sessionRegistry.getOrCreate(scope);
|
|
971
1038
|
const { session, promptQueue } = entry;
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
console.log("[gateway] new thread session — prepending title", {
|
|
978
|
-
scope,
|
|
979
|
-
threadName: thread.name
|
|
980
|
-
});
|
|
981
|
-
}
|
|
1039
|
+
if (created && scope.startsWith("thread:") && message.channel.isThread()) {
|
|
1040
|
+
console.log("[gateway] new thread session", {
|
|
1041
|
+
scope,
|
|
1042
|
+
threadName: message.channel.name
|
|
1043
|
+
});
|
|
982
1044
|
}
|
|
983
1045
|
let typingInterval = null;
|
|
984
1046
|
if (message.channel.isSendable()) {
|
|
@@ -1033,7 +1095,8 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1033
1095
|
}
|
|
1034
1096
|
const response = await promptQueue.enqueue(async () => {
|
|
1035
1097
|
console.log(`[queue] processing message ${message.id} in scope ${scope}`);
|
|
1036
|
-
const
|
|
1098
|
+
const promptContent = buildDiscordPromptContent(message, scope, content, config);
|
|
1099
|
+
const transformedPrompt = await config.promptTransform(promptContent);
|
|
1037
1100
|
return collectReply(session, transformedPrompt, {
|
|
1038
1101
|
logPrefix: `[agent:${session.sessionId}]`
|
|
1039
1102
|
});
|
|
@@ -1153,27 +1216,6 @@ class SessionRegistry {
|
|
|
1153
1216
|
}
|
|
1154
1217
|
}
|
|
1155
1218
|
|
|
1156
|
-
// src/prompt-context.ts
|
|
1157
|
-
function buildTimeContextPrompt(userMessage, options = {}) {
|
|
1158
|
-
const timeZone = options.timeZone || "UTC";
|
|
1159
|
-
const locale = options.locale || "en-AU";
|
|
1160
|
-
const now = options.now || new Date;
|
|
1161
|
-
const localTime = new Intl.DateTimeFormat(locale, {
|
|
1162
|
-
timeZone,
|
|
1163
|
-
weekday: "short",
|
|
1164
|
-
day: "numeric",
|
|
1165
|
-
month: "short",
|
|
1166
|
-
year: "2-digit",
|
|
1167
|
-
hour: "2-digit",
|
|
1168
|
-
minute: "2-digit",
|
|
1169
|
-
hour12: false,
|
|
1170
|
-
timeZoneName: "short"
|
|
1171
|
-
}).format(now);
|
|
1172
|
-
const trimmedMessage = userMessage.trim();
|
|
1173
|
-
return [`<time>${localTime}</time>`, "", trimmedMessage].join(`
|
|
1174
|
-
`);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
1219
|
// src/index.ts
|
|
1178
1220
|
async function startDiscordGateway(config) {
|
|
1179
1221
|
const resolvedConfig = resolveGatewayConfig(config);
|
|
@@ -1241,5 +1283,6 @@ export {
|
|
|
1241
1283
|
resolveConfig,
|
|
1242
1284
|
loadDiscordPiBridgeConfigFromEnv,
|
|
1243
1285
|
loadDiscordGatewayConfigFromEnv,
|
|
1244
|
-
|
|
1286
|
+
formatDiscordPromptTime,
|
|
1287
|
+
buildDiscordMessageContextPrompt
|
|
1245
1288
|
};
|
package/dist/prompt-context.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type DiscordPromptTimeFormatOptions = {
|
|
2
2
|
timeZone?: string;
|
|
3
3
|
locale?: string;
|
|
4
|
-
now?: Date;
|
|
5
4
|
};
|
|
6
|
-
export
|
|
5
|
+
export type DiscordPromptScope = "dm" | "thread";
|
|
6
|
+
export type DiscordMessageContextPromptOptions = {
|
|
7
|
+
scope: DiscordPromptScope;
|
|
8
|
+
messageId: string;
|
|
9
|
+
authorId: string;
|
|
10
|
+
sentAt?: string;
|
|
11
|
+
sentAtLocal?: string;
|
|
12
|
+
authorName?: string;
|
|
13
|
+
threadId?: string;
|
|
14
|
+
threadTitle?: string;
|
|
15
|
+
forumChannelId?: string | null;
|
|
16
|
+
};
|
|
17
|
+
export declare function buildDiscordMessageContextPrompt(userMessage: string, options: DiscordMessageContextPromptOptions): string;
|
|
18
|
+
export declare function formatDiscordPromptTime(date: Date, options?: DiscordPromptTimeFormatOptions): string;
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type DiscordPiBridgeConfig = {
|
|
|
9
9
|
modelProvider?: string;
|
|
10
10
|
modelId?: string;
|
|
11
11
|
thinkingLevel?: ThinkingLevel;
|
|
12
|
+
promptTimeZone?: string;
|
|
13
|
+
promptLocale?: string;
|
|
12
14
|
promptTransform?: PromptTransform;
|
|
13
15
|
startupMessage?: string | false;
|
|
14
16
|
shutdownOnSignals?: boolean;
|
|
@@ -21,6 +23,8 @@ export type ResolvedDiscordPiBridgeConfig = {
|
|
|
21
23
|
modelProvider: string;
|
|
22
24
|
modelId: string;
|
|
23
25
|
thinkingLevel: ThinkingLevel;
|
|
26
|
+
promptTimeZone: string;
|
|
27
|
+
promptLocale: string;
|
|
24
28
|
promptTransform: PromptTransform;
|
|
25
29
|
startupMessage: string | false;
|
|
26
30
|
shutdownOnSignals: boolean;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|