@canonmsg/backend-contracts 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/dist/agentBehaviorPolicy.d.ts +59 -0
- package/dist/agentBehaviorPolicy.js +240 -0
- package/dist/cjs/agentBehaviorPolicy.js +250 -0
- package/dist/cjs/contactRequest.js +72 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/media.js +101 -0
- package/dist/cjs/message.js +122 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/turnProtocol.js +83 -0
- package/dist/contactRequest.d.ts +20 -0
- package/dist/contactRequest.js +69 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/media.d.ts +24 -0
- package/dist/media.js +93 -0
- package/dist/message.d.ts +47 -0
- package/dist/message.js +119 -0
- package/dist/turnProtocol.d.ts +32 -0
- package/dist/turnProtocol.js +75 -0
- package/package.json +48 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type ParticipationStyle = 'natural' | 'collaborative' | 'mention-first' | 'approval-gated' | 'handoff-only' | 'observer';
|
|
2
|
+
export interface AgentBehaviorSettingsRecord {
|
|
3
|
+
participationStyle?: ParticipationStyle;
|
|
4
|
+
allowAgentToAgent?: boolean;
|
|
5
|
+
allowLongRunningCollaboration?: boolean;
|
|
6
|
+
requireMentionForGroupReplies?: boolean;
|
|
7
|
+
maxConsecutiveAgentTurns?: number | null;
|
|
8
|
+
instructions?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ResolvedAgentBehaviorPolicyRecord {
|
|
11
|
+
participation: {
|
|
12
|
+
style: ParticipationStyle;
|
|
13
|
+
allowAgentToAgent: boolean;
|
|
14
|
+
allowHumanToAgent: boolean;
|
|
15
|
+
allowLongRunningCollaboration: boolean;
|
|
16
|
+
requireMentionForGroupReplies: boolean;
|
|
17
|
+
maxConsecutiveAgentTurns?: number | null;
|
|
18
|
+
};
|
|
19
|
+
instructions: string[];
|
|
20
|
+
source: {
|
|
21
|
+
hasAgentDefault: boolean;
|
|
22
|
+
hasConversationOverride: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface ParticipationDecisionInput {
|
|
26
|
+
conversationType: 'direct' | 'group' | 'unknown';
|
|
27
|
+
senderType: 'human' | 'ai_agent';
|
|
28
|
+
isOwner: boolean;
|
|
29
|
+
mentionedAgent: boolean;
|
|
30
|
+
recentHumanCount?: number;
|
|
31
|
+
consecutiveAgentTurns?: number;
|
|
32
|
+
currentAgentStreakStartedByHuman?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ParticipationHistoryMessage {
|
|
35
|
+
senderId: string;
|
|
36
|
+
senderType: 'human' | 'ai_agent';
|
|
37
|
+
metadata?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface ParticipationHistorySnapshot {
|
|
40
|
+
recentSenderTypes: Array<'human' | 'ai_agent'>;
|
|
41
|
+
recentHumanCount: number;
|
|
42
|
+
recentAgentCount: number;
|
|
43
|
+
consecutiveAgentTurns: number;
|
|
44
|
+
currentAgentStreakStartedByHuman: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare const PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
|
|
47
|
+
export declare function parseAgentBehaviorSettings(raw: unknown): AgentBehaviorSettingsRecord;
|
|
48
|
+
export declare function normalizeStoredAgentBehaviorPolicy(raw: Record<string, unknown> | undefined): AgentBehaviorSettingsRecord | null;
|
|
49
|
+
export declare function normalizeAgentBehaviorInstructions(value: string | null | undefined): string | null;
|
|
50
|
+
export declare function resolveAgentBehaviorPolicy(params?: {
|
|
51
|
+
agentDefault?: AgentBehaviorSettingsRecord | null;
|
|
52
|
+
conversationOverride?: AgentBehaviorSettingsRecord | null;
|
|
53
|
+
}): ResolvedAgentBehaviorPolicyRecord;
|
|
54
|
+
export declare function buildParticipationHistorySnapshot(messages: ParticipationHistoryMessage[], agentId?: string): ParticipationHistorySnapshot;
|
|
55
|
+
export declare function appendParticipationHistoryMessage(snapshot: ParticipationHistorySnapshot, message: ParticipationHistoryMessage, limit?: number): ParticipationHistorySnapshot;
|
|
56
|
+
export declare function evaluateParticipationPolicy(policy: ResolvedAgentBehaviorPolicyRecord, input: ParticipationDecisionInput): {
|
|
57
|
+
allow: boolean;
|
|
58
|
+
reason: string;
|
|
59
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { resolveTurnMessageSemantics } from './turnProtocol.js';
|
|
2
|
+
export const PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
|
|
3
|
+
const VALID_PARTICIPATION_STYLES = new Set([
|
|
4
|
+
'natural',
|
|
5
|
+
'collaborative',
|
|
6
|
+
'mention-first',
|
|
7
|
+
'approval-gated',
|
|
8
|
+
'handoff-only',
|
|
9
|
+
'observer',
|
|
10
|
+
]);
|
|
11
|
+
const DEFAULT_POLICY = {
|
|
12
|
+
participation: {
|
|
13
|
+
style: 'natural',
|
|
14
|
+
allowAgentToAgent: true,
|
|
15
|
+
allowHumanToAgent: true,
|
|
16
|
+
allowLongRunningCollaboration: true,
|
|
17
|
+
requireMentionForGroupReplies: false,
|
|
18
|
+
maxConsecutiveAgentTurns: null,
|
|
19
|
+
},
|
|
20
|
+
instructions: [],
|
|
21
|
+
source: {
|
|
22
|
+
hasAgentDefault: false,
|
|
23
|
+
hasConversationOverride: false,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
function normalizeString(value) {
|
|
27
|
+
if (typeof value !== 'string')
|
|
28
|
+
return undefined;
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
31
|
+
}
|
|
32
|
+
function normalizeOptionalBoolean(value, fieldName) {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (typeof value !== 'boolean') {
|
|
36
|
+
throw new Error(`${fieldName} must be a boolean`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function normalizeMaxConsecutiveAgentTurns(value) {
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (value === null || value === '')
|
|
44
|
+
return null;
|
|
45
|
+
if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 20) {
|
|
46
|
+
throw new Error('maxConsecutiveAgentTurns must be an integer between 1 and 20, or null');
|
|
47
|
+
}
|
|
48
|
+
return Number(value);
|
|
49
|
+
}
|
|
50
|
+
function resolveGroupMentionRequirement(input) {
|
|
51
|
+
if (input.requireMentionForGroupReplies !== undefined
|
|
52
|
+
&& typeof input.requireMentionForGroupReplies !== 'boolean') {
|
|
53
|
+
throw new Error('requireMentionForGroupReplies must be a boolean');
|
|
54
|
+
}
|
|
55
|
+
if (input.requireMentionForGroupAgentReplies !== undefined
|
|
56
|
+
&& typeof input.requireMentionForGroupAgentReplies !== 'boolean') {
|
|
57
|
+
throw new Error('requireMentionForGroupAgentReplies must be a boolean');
|
|
58
|
+
}
|
|
59
|
+
if (typeof input.requireMentionForGroupReplies === 'boolean') {
|
|
60
|
+
return input.requireMentionForGroupReplies;
|
|
61
|
+
}
|
|
62
|
+
if (typeof input.requireMentionForGroupAgentReplies === 'boolean') {
|
|
63
|
+
return input.requireMentionForGroupAgentReplies;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
export function parseAgentBehaviorSettings(raw) {
|
|
68
|
+
const input = raw && typeof raw === 'object' ? raw : {};
|
|
69
|
+
const participationStyle = normalizeString(input.participationStyle);
|
|
70
|
+
if (participationStyle && !VALID_PARTICIPATION_STYLES.has(participationStyle)) {
|
|
71
|
+
throw new Error('Invalid participationStyle');
|
|
72
|
+
}
|
|
73
|
+
const instructions = input.instructions === undefined
|
|
74
|
+
? undefined
|
|
75
|
+
: input.instructions === null
|
|
76
|
+
? null
|
|
77
|
+
: normalizeString(input.instructions) ?? null;
|
|
78
|
+
if (typeof instructions === 'string' && instructions.length > 2_000) {
|
|
79
|
+
throw new Error('instructions must be at most 2000 characters');
|
|
80
|
+
}
|
|
81
|
+
const allowAgentToAgent = normalizeOptionalBoolean(input.allowAgentToAgent, 'allowAgentToAgent');
|
|
82
|
+
const allowLongRunningCollaboration = normalizeOptionalBoolean(input.allowLongRunningCollaboration, 'allowLongRunningCollaboration');
|
|
83
|
+
const requireMentionForGroupReplies = resolveGroupMentionRequirement(input);
|
|
84
|
+
const maxConsecutiveAgentTurns = normalizeMaxConsecutiveAgentTurns(input.maxConsecutiveAgentTurns);
|
|
85
|
+
return {
|
|
86
|
+
...(participationStyle ? { participationStyle: participationStyle } : {}),
|
|
87
|
+
...(allowAgentToAgent !== undefined ? { allowAgentToAgent } : {}),
|
|
88
|
+
...(allowLongRunningCollaboration !== undefined ? { allowLongRunningCollaboration } : {}),
|
|
89
|
+
...(requireMentionForGroupReplies !== undefined ? { requireMentionForGroupReplies } : {}),
|
|
90
|
+
...(maxConsecutiveAgentTurns !== undefined ? { maxConsecutiveAgentTurns } : {}),
|
|
91
|
+
...(instructions !== undefined ? { instructions } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function normalizeStoredAgentBehaviorPolicy(raw) {
|
|
95
|
+
if (!raw)
|
|
96
|
+
return null;
|
|
97
|
+
return {
|
|
98
|
+
...(typeof raw.participationStyle === 'string' && VALID_PARTICIPATION_STYLES.has(raw.participationStyle)
|
|
99
|
+
? { participationStyle: raw.participationStyle }
|
|
100
|
+
: {}),
|
|
101
|
+
...(typeof raw.allowAgentToAgent === 'boolean' ? { allowAgentToAgent: raw.allowAgentToAgent } : {}),
|
|
102
|
+
...(typeof raw.allowLongRunningCollaboration === 'boolean'
|
|
103
|
+
? { allowLongRunningCollaboration: raw.allowLongRunningCollaboration }
|
|
104
|
+
: {}),
|
|
105
|
+
...(typeof raw.requireMentionForGroupReplies === 'boolean'
|
|
106
|
+
? { requireMentionForGroupReplies: raw.requireMentionForGroupReplies }
|
|
107
|
+
: typeof raw.requireMentionForGroupAgentReplies === 'boolean'
|
|
108
|
+
? { requireMentionForGroupReplies: raw.requireMentionForGroupAgentReplies }
|
|
109
|
+
: {}),
|
|
110
|
+
...(typeof raw.maxConsecutiveAgentTurns === 'number' || raw.maxConsecutiveAgentTurns === null
|
|
111
|
+
? { maxConsecutiveAgentTurns: raw.maxConsecutiveAgentTurns }
|
|
112
|
+
: {}),
|
|
113
|
+
...(typeof raw.instructions === 'string' || raw.instructions === null
|
|
114
|
+
? { instructions: raw.instructions }
|
|
115
|
+
: {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function coalesceBoolean(overrideValue, fallbackValue, defaultValue) {
|
|
119
|
+
if (typeof overrideValue === 'boolean')
|
|
120
|
+
return overrideValue;
|
|
121
|
+
if (typeof fallbackValue === 'boolean')
|
|
122
|
+
return fallbackValue;
|
|
123
|
+
return defaultValue;
|
|
124
|
+
}
|
|
125
|
+
export function normalizeAgentBehaviorInstructions(value) {
|
|
126
|
+
if (typeof value !== 'string')
|
|
127
|
+
return null;
|
|
128
|
+
const trimmed = value.trim();
|
|
129
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
130
|
+
}
|
|
131
|
+
export function resolveAgentBehaviorPolicy(params) {
|
|
132
|
+
const agentDefault = params?.agentDefault ?? null;
|
|
133
|
+
const conversationOverride = params?.conversationOverride ?? null;
|
|
134
|
+
const defaultInstructions = normalizeAgentBehaviorInstructions(agentDefault?.instructions);
|
|
135
|
+
const overrideInstructions = normalizeAgentBehaviorInstructions(conversationOverride?.instructions);
|
|
136
|
+
return {
|
|
137
|
+
participation: {
|
|
138
|
+
style: conversationOverride?.participationStyle
|
|
139
|
+
?? agentDefault?.participationStyle
|
|
140
|
+
?? DEFAULT_POLICY.participation.style,
|
|
141
|
+
allowAgentToAgent: coalesceBoolean(conversationOverride?.allowAgentToAgent, agentDefault?.allowAgentToAgent, DEFAULT_POLICY.participation.allowAgentToAgent),
|
|
142
|
+
allowHumanToAgent: DEFAULT_POLICY.participation.allowHumanToAgent,
|
|
143
|
+
allowLongRunningCollaboration: coalesceBoolean(conversationOverride?.allowLongRunningCollaboration, agentDefault?.allowLongRunningCollaboration, DEFAULT_POLICY.participation.allowLongRunningCollaboration),
|
|
144
|
+
requireMentionForGroupReplies: coalesceBoolean(conversationOverride?.requireMentionForGroupReplies, agentDefault?.requireMentionForGroupReplies, DEFAULT_POLICY.participation.requireMentionForGroupReplies),
|
|
145
|
+
maxConsecutiveAgentTurns: conversationOverride?.maxConsecutiveAgentTurns !== undefined
|
|
146
|
+
? conversationOverride.maxConsecutiveAgentTurns ?? null
|
|
147
|
+
: agentDefault?.maxConsecutiveAgentTurns !== undefined
|
|
148
|
+
? agentDefault.maxConsecutiveAgentTurns ?? null
|
|
149
|
+
: DEFAULT_POLICY.participation.maxConsecutiveAgentTurns,
|
|
150
|
+
},
|
|
151
|
+
instructions: [
|
|
152
|
+
...(defaultInstructions ? [defaultInstructions] : []),
|
|
153
|
+
...(overrideInstructions ? [overrideInstructions] : []),
|
|
154
|
+
],
|
|
155
|
+
source: {
|
|
156
|
+
hasAgentDefault: agentDefault != null,
|
|
157
|
+
hasConversationOverride: conversationOverride != null,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function toHistorySenderType(message) {
|
|
162
|
+
if (message.senderType !== 'ai_agent') {
|
|
163
|
+
return 'human';
|
|
164
|
+
}
|
|
165
|
+
return resolveTurnMessageSemantics({
|
|
166
|
+
senderType: message.senderType,
|
|
167
|
+
metadata: message.metadata,
|
|
168
|
+
}) === 'turn_complete'
|
|
169
|
+
? 'ai_agent'
|
|
170
|
+
: null;
|
|
171
|
+
}
|
|
172
|
+
function buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes) {
|
|
173
|
+
let consecutiveAgentTurns = 0;
|
|
174
|
+
for (const senderType of recentSenderTypes) {
|
|
175
|
+
if (senderType !== 'ai_agent')
|
|
176
|
+
break;
|
|
177
|
+
consecutiveAgentTurns += 1;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
recentSenderTypes,
|
|
181
|
+
recentHumanCount: recentSenderTypes.filter((senderType) => senderType === 'human').length,
|
|
182
|
+
recentAgentCount: recentSenderTypes.filter((senderType) => senderType === 'ai_agent').length,
|
|
183
|
+
consecutiveAgentTurns,
|
|
184
|
+
currentAgentStreakStartedByHuman: consecutiveAgentTurns > 0 && recentSenderTypes[consecutiveAgentTurns] === 'human',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function buildParticipationHistorySnapshot(messages, agentId) {
|
|
188
|
+
void agentId;
|
|
189
|
+
const recentSenderTypes = messages
|
|
190
|
+
.flatMap((message) => {
|
|
191
|
+
const senderType = toHistorySenderType(message);
|
|
192
|
+
return senderType ? [senderType] : [];
|
|
193
|
+
});
|
|
194
|
+
return buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes);
|
|
195
|
+
}
|
|
196
|
+
export function appendParticipationHistoryMessage(snapshot, message, limit = PARTICIPATION_HISTORY_FETCH_LIMIT) {
|
|
197
|
+
const senderType = toHistorySenderType(message);
|
|
198
|
+
if (!senderType) {
|
|
199
|
+
return snapshot;
|
|
200
|
+
}
|
|
201
|
+
return buildParticipationHistorySnapshotFromSenderTypes([
|
|
202
|
+
senderType,
|
|
203
|
+
...snapshot.recentSenderTypes,
|
|
204
|
+
].slice(0, limit));
|
|
205
|
+
}
|
|
206
|
+
export function evaluateParticipationPolicy(policy, input) {
|
|
207
|
+
const consecutiveAgentTurns = Math.max(input.consecutiveAgentTurns ?? (input.senderType === 'ai_agent' ? 1 : 0), input.senderType === 'ai_agent' ? 1 : 0);
|
|
208
|
+
const currentAgentStreakStartedByHuman = input.currentAgentStreakStartedByHuman === true;
|
|
209
|
+
if (input.isOwner) {
|
|
210
|
+
return { allow: true, reason: 'owner messages always pass through' };
|
|
211
|
+
}
|
|
212
|
+
if (input.conversationType === 'group'
|
|
213
|
+
&& policy.participation.requireMentionForGroupReplies
|
|
214
|
+
&& !input.mentionedAgent) {
|
|
215
|
+
return {
|
|
216
|
+
allow: false,
|
|
217
|
+
reason: 'group replies require a direct mention',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (input.senderType !== 'ai_agent') {
|
|
221
|
+
return { allow: true, reason: 'latest sender is human' };
|
|
222
|
+
}
|
|
223
|
+
if (!policy.participation.allowAgentToAgent) {
|
|
224
|
+
return { allow: false, reason: 'agent-to-agent participation disabled by policy' };
|
|
225
|
+
}
|
|
226
|
+
if (!policy.participation.allowLongRunningCollaboration
|
|
227
|
+
&& (consecutiveAgentTurns > 1
|
|
228
|
+
|| !currentAgentStreakStartedByHuman)) {
|
|
229
|
+
return {
|
|
230
|
+
allow: false,
|
|
231
|
+
reason: 'a fresh human steer is required before continuing agent collaboration',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (typeof policy.participation.maxConsecutiveAgentTurns === 'number'
|
|
235
|
+
&& policy.participation.maxConsecutiveAgentTurns >= 0
|
|
236
|
+
&& consecutiveAgentTurns > policy.participation.maxConsecutiveAgentTurns) {
|
|
237
|
+
return { allow: false, reason: 'maximum consecutive agent turns reached' };
|
|
238
|
+
}
|
|
239
|
+
return { allow: true, reason: 'agent-to-agent participation allowed by policy' };
|
|
240
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PARTICIPATION_HISTORY_FETCH_LIMIT = void 0;
|
|
4
|
+
exports.parseAgentBehaviorSettings = parseAgentBehaviorSettings;
|
|
5
|
+
exports.normalizeStoredAgentBehaviorPolicy = normalizeStoredAgentBehaviorPolicy;
|
|
6
|
+
exports.normalizeAgentBehaviorInstructions = normalizeAgentBehaviorInstructions;
|
|
7
|
+
exports.resolveAgentBehaviorPolicy = resolveAgentBehaviorPolicy;
|
|
8
|
+
exports.buildParticipationHistorySnapshot = buildParticipationHistorySnapshot;
|
|
9
|
+
exports.appendParticipationHistoryMessage = appendParticipationHistoryMessage;
|
|
10
|
+
exports.evaluateParticipationPolicy = evaluateParticipationPolicy;
|
|
11
|
+
const turnProtocol_js_1 = require("./turnProtocol.js");
|
|
12
|
+
exports.PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
|
|
13
|
+
const VALID_PARTICIPATION_STYLES = new Set([
|
|
14
|
+
'natural',
|
|
15
|
+
'collaborative',
|
|
16
|
+
'mention-first',
|
|
17
|
+
'approval-gated',
|
|
18
|
+
'handoff-only',
|
|
19
|
+
'observer',
|
|
20
|
+
]);
|
|
21
|
+
const DEFAULT_POLICY = {
|
|
22
|
+
participation: {
|
|
23
|
+
style: 'natural',
|
|
24
|
+
allowAgentToAgent: true,
|
|
25
|
+
allowHumanToAgent: true,
|
|
26
|
+
allowLongRunningCollaboration: true,
|
|
27
|
+
requireMentionForGroupReplies: false,
|
|
28
|
+
maxConsecutiveAgentTurns: null,
|
|
29
|
+
},
|
|
30
|
+
instructions: [],
|
|
31
|
+
source: {
|
|
32
|
+
hasAgentDefault: false,
|
|
33
|
+
hasConversationOverride: false,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
function normalizeString(value) {
|
|
37
|
+
if (typeof value !== 'string')
|
|
38
|
+
return undefined;
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
41
|
+
}
|
|
42
|
+
function normalizeOptionalBoolean(value, fieldName) {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (typeof value !== 'boolean') {
|
|
46
|
+
throw new Error(`${fieldName} must be a boolean`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function normalizeMaxConsecutiveAgentTurns(value) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (value === null || value === '')
|
|
54
|
+
return null;
|
|
55
|
+
if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 20) {
|
|
56
|
+
throw new Error('maxConsecutiveAgentTurns must be an integer between 1 and 20, or null');
|
|
57
|
+
}
|
|
58
|
+
return Number(value);
|
|
59
|
+
}
|
|
60
|
+
function resolveGroupMentionRequirement(input) {
|
|
61
|
+
if (input.requireMentionForGroupReplies !== undefined
|
|
62
|
+
&& typeof input.requireMentionForGroupReplies !== 'boolean') {
|
|
63
|
+
throw new Error('requireMentionForGroupReplies must be a boolean');
|
|
64
|
+
}
|
|
65
|
+
if (input.requireMentionForGroupAgentReplies !== undefined
|
|
66
|
+
&& typeof input.requireMentionForGroupAgentReplies !== 'boolean') {
|
|
67
|
+
throw new Error('requireMentionForGroupAgentReplies must be a boolean');
|
|
68
|
+
}
|
|
69
|
+
if (typeof input.requireMentionForGroupReplies === 'boolean') {
|
|
70
|
+
return input.requireMentionForGroupReplies;
|
|
71
|
+
}
|
|
72
|
+
if (typeof input.requireMentionForGroupAgentReplies === 'boolean') {
|
|
73
|
+
return input.requireMentionForGroupAgentReplies;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
function parseAgentBehaviorSettings(raw) {
|
|
78
|
+
const input = raw && typeof raw === 'object' ? raw : {};
|
|
79
|
+
const participationStyle = normalizeString(input.participationStyle);
|
|
80
|
+
if (participationStyle && !VALID_PARTICIPATION_STYLES.has(participationStyle)) {
|
|
81
|
+
throw new Error('Invalid participationStyle');
|
|
82
|
+
}
|
|
83
|
+
const instructions = input.instructions === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: input.instructions === null
|
|
86
|
+
? null
|
|
87
|
+
: normalizeString(input.instructions) ?? null;
|
|
88
|
+
if (typeof instructions === 'string' && instructions.length > 2_000) {
|
|
89
|
+
throw new Error('instructions must be at most 2000 characters');
|
|
90
|
+
}
|
|
91
|
+
const allowAgentToAgent = normalizeOptionalBoolean(input.allowAgentToAgent, 'allowAgentToAgent');
|
|
92
|
+
const allowLongRunningCollaboration = normalizeOptionalBoolean(input.allowLongRunningCollaboration, 'allowLongRunningCollaboration');
|
|
93
|
+
const requireMentionForGroupReplies = resolveGroupMentionRequirement(input);
|
|
94
|
+
const maxConsecutiveAgentTurns = normalizeMaxConsecutiveAgentTurns(input.maxConsecutiveAgentTurns);
|
|
95
|
+
return {
|
|
96
|
+
...(participationStyle ? { participationStyle: participationStyle } : {}),
|
|
97
|
+
...(allowAgentToAgent !== undefined ? { allowAgentToAgent } : {}),
|
|
98
|
+
...(allowLongRunningCollaboration !== undefined ? { allowLongRunningCollaboration } : {}),
|
|
99
|
+
...(requireMentionForGroupReplies !== undefined ? { requireMentionForGroupReplies } : {}),
|
|
100
|
+
...(maxConsecutiveAgentTurns !== undefined ? { maxConsecutiveAgentTurns } : {}),
|
|
101
|
+
...(instructions !== undefined ? { instructions } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function normalizeStoredAgentBehaviorPolicy(raw) {
|
|
105
|
+
if (!raw)
|
|
106
|
+
return null;
|
|
107
|
+
return {
|
|
108
|
+
...(typeof raw.participationStyle === 'string' && VALID_PARTICIPATION_STYLES.has(raw.participationStyle)
|
|
109
|
+
? { participationStyle: raw.participationStyle }
|
|
110
|
+
: {}),
|
|
111
|
+
...(typeof raw.allowAgentToAgent === 'boolean' ? { allowAgentToAgent: raw.allowAgentToAgent } : {}),
|
|
112
|
+
...(typeof raw.allowLongRunningCollaboration === 'boolean'
|
|
113
|
+
? { allowLongRunningCollaboration: raw.allowLongRunningCollaboration }
|
|
114
|
+
: {}),
|
|
115
|
+
...(typeof raw.requireMentionForGroupReplies === 'boolean'
|
|
116
|
+
? { requireMentionForGroupReplies: raw.requireMentionForGroupReplies }
|
|
117
|
+
: typeof raw.requireMentionForGroupAgentReplies === 'boolean'
|
|
118
|
+
? { requireMentionForGroupReplies: raw.requireMentionForGroupAgentReplies }
|
|
119
|
+
: {}),
|
|
120
|
+
...(typeof raw.maxConsecutiveAgentTurns === 'number' || raw.maxConsecutiveAgentTurns === null
|
|
121
|
+
? { maxConsecutiveAgentTurns: raw.maxConsecutiveAgentTurns }
|
|
122
|
+
: {}),
|
|
123
|
+
...(typeof raw.instructions === 'string' || raw.instructions === null
|
|
124
|
+
? { instructions: raw.instructions }
|
|
125
|
+
: {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function coalesceBoolean(overrideValue, fallbackValue, defaultValue) {
|
|
129
|
+
if (typeof overrideValue === 'boolean')
|
|
130
|
+
return overrideValue;
|
|
131
|
+
if (typeof fallbackValue === 'boolean')
|
|
132
|
+
return fallbackValue;
|
|
133
|
+
return defaultValue;
|
|
134
|
+
}
|
|
135
|
+
function normalizeAgentBehaviorInstructions(value) {
|
|
136
|
+
if (typeof value !== 'string')
|
|
137
|
+
return null;
|
|
138
|
+
const trimmed = value.trim();
|
|
139
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
140
|
+
}
|
|
141
|
+
function resolveAgentBehaviorPolicy(params) {
|
|
142
|
+
const agentDefault = params?.agentDefault ?? null;
|
|
143
|
+
const conversationOverride = params?.conversationOverride ?? null;
|
|
144
|
+
const defaultInstructions = normalizeAgentBehaviorInstructions(agentDefault?.instructions);
|
|
145
|
+
const overrideInstructions = normalizeAgentBehaviorInstructions(conversationOverride?.instructions);
|
|
146
|
+
return {
|
|
147
|
+
participation: {
|
|
148
|
+
style: conversationOverride?.participationStyle
|
|
149
|
+
?? agentDefault?.participationStyle
|
|
150
|
+
?? DEFAULT_POLICY.participation.style,
|
|
151
|
+
allowAgentToAgent: coalesceBoolean(conversationOverride?.allowAgentToAgent, agentDefault?.allowAgentToAgent, DEFAULT_POLICY.participation.allowAgentToAgent),
|
|
152
|
+
allowHumanToAgent: DEFAULT_POLICY.participation.allowHumanToAgent,
|
|
153
|
+
allowLongRunningCollaboration: coalesceBoolean(conversationOverride?.allowLongRunningCollaboration, agentDefault?.allowLongRunningCollaboration, DEFAULT_POLICY.participation.allowLongRunningCollaboration),
|
|
154
|
+
requireMentionForGroupReplies: coalesceBoolean(conversationOverride?.requireMentionForGroupReplies, agentDefault?.requireMentionForGroupReplies, DEFAULT_POLICY.participation.requireMentionForGroupReplies),
|
|
155
|
+
maxConsecutiveAgentTurns: conversationOverride?.maxConsecutiveAgentTurns !== undefined
|
|
156
|
+
? conversationOverride.maxConsecutiveAgentTurns ?? null
|
|
157
|
+
: agentDefault?.maxConsecutiveAgentTurns !== undefined
|
|
158
|
+
? agentDefault.maxConsecutiveAgentTurns ?? null
|
|
159
|
+
: DEFAULT_POLICY.participation.maxConsecutiveAgentTurns,
|
|
160
|
+
},
|
|
161
|
+
instructions: [
|
|
162
|
+
...(defaultInstructions ? [defaultInstructions] : []),
|
|
163
|
+
...(overrideInstructions ? [overrideInstructions] : []),
|
|
164
|
+
],
|
|
165
|
+
source: {
|
|
166
|
+
hasAgentDefault: agentDefault != null,
|
|
167
|
+
hasConversationOverride: conversationOverride != null,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function toHistorySenderType(message) {
|
|
172
|
+
if (message.senderType !== 'ai_agent') {
|
|
173
|
+
return 'human';
|
|
174
|
+
}
|
|
175
|
+
return (0, turnProtocol_js_1.resolveTurnMessageSemantics)({
|
|
176
|
+
senderType: message.senderType,
|
|
177
|
+
metadata: message.metadata,
|
|
178
|
+
}) === 'turn_complete'
|
|
179
|
+
? 'ai_agent'
|
|
180
|
+
: null;
|
|
181
|
+
}
|
|
182
|
+
function buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes) {
|
|
183
|
+
let consecutiveAgentTurns = 0;
|
|
184
|
+
for (const senderType of recentSenderTypes) {
|
|
185
|
+
if (senderType !== 'ai_agent')
|
|
186
|
+
break;
|
|
187
|
+
consecutiveAgentTurns += 1;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
recentSenderTypes,
|
|
191
|
+
recentHumanCount: recentSenderTypes.filter((senderType) => senderType === 'human').length,
|
|
192
|
+
recentAgentCount: recentSenderTypes.filter((senderType) => senderType === 'ai_agent').length,
|
|
193
|
+
consecutiveAgentTurns,
|
|
194
|
+
currentAgentStreakStartedByHuman: consecutiveAgentTurns > 0 && recentSenderTypes[consecutiveAgentTurns] === 'human',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function buildParticipationHistorySnapshot(messages, agentId) {
|
|
198
|
+
void agentId;
|
|
199
|
+
const recentSenderTypes = messages
|
|
200
|
+
.flatMap((message) => {
|
|
201
|
+
const senderType = toHistorySenderType(message);
|
|
202
|
+
return senderType ? [senderType] : [];
|
|
203
|
+
});
|
|
204
|
+
return buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes);
|
|
205
|
+
}
|
|
206
|
+
function appendParticipationHistoryMessage(snapshot, message, limit = exports.PARTICIPATION_HISTORY_FETCH_LIMIT) {
|
|
207
|
+
const senderType = toHistorySenderType(message);
|
|
208
|
+
if (!senderType) {
|
|
209
|
+
return snapshot;
|
|
210
|
+
}
|
|
211
|
+
return buildParticipationHistorySnapshotFromSenderTypes([
|
|
212
|
+
senderType,
|
|
213
|
+
...snapshot.recentSenderTypes,
|
|
214
|
+
].slice(0, limit));
|
|
215
|
+
}
|
|
216
|
+
function evaluateParticipationPolicy(policy, input) {
|
|
217
|
+
const consecutiveAgentTurns = Math.max(input.consecutiveAgentTurns ?? (input.senderType === 'ai_agent' ? 1 : 0), input.senderType === 'ai_agent' ? 1 : 0);
|
|
218
|
+
const currentAgentStreakStartedByHuman = input.currentAgentStreakStartedByHuman === true;
|
|
219
|
+
if (input.isOwner) {
|
|
220
|
+
return { allow: true, reason: 'owner messages always pass through' };
|
|
221
|
+
}
|
|
222
|
+
if (input.conversationType === 'group'
|
|
223
|
+
&& policy.participation.requireMentionForGroupReplies
|
|
224
|
+
&& !input.mentionedAgent) {
|
|
225
|
+
return {
|
|
226
|
+
allow: false,
|
|
227
|
+
reason: 'group replies require a direct mention',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (input.senderType !== 'ai_agent') {
|
|
231
|
+
return { allow: true, reason: 'latest sender is human' };
|
|
232
|
+
}
|
|
233
|
+
if (!policy.participation.allowAgentToAgent) {
|
|
234
|
+
return { allow: false, reason: 'agent-to-agent participation disabled by policy' };
|
|
235
|
+
}
|
|
236
|
+
if (!policy.participation.allowLongRunningCollaboration
|
|
237
|
+
&& (consecutiveAgentTurns > 1
|
|
238
|
+
|| !currentAgentStreakStartedByHuman)) {
|
|
239
|
+
return {
|
|
240
|
+
allow: false,
|
|
241
|
+
reason: 'a fresh human steer is required before continuing agent collaboration',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (typeof policy.participation.maxConsecutiveAgentTurns === 'number'
|
|
245
|
+
&& policy.participation.maxConsecutiveAgentTurns >= 0
|
|
246
|
+
&& consecutiveAgentTurns > policy.participation.maxConsecutiveAgentTurns) {
|
|
247
|
+
return { allow: false, reason: 'maximum consecutive agent turns reached' };
|
|
248
|
+
}
|
|
249
|
+
return { allow: true, reason: 'agent-to-agent participation allowed by policy' };
|
|
250
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeContactRequest = serializeContactRequest;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function normalizeTimestamp(value) {
|
|
8
|
+
if (value instanceof Date)
|
|
9
|
+
return value.toISOString();
|
|
10
|
+
if (isRecord(value) && typeof value.toDate === 'function') {
|
|
11
|
+
const result = value.toDate();
|
|
12
|
+
if (result instanceof Date)
|
|
13
|
+
return result.toISOString();
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function normalizeStatus(value) {
|
|
18
|
+
if (value === 'pending'
|
|
19
|
+
|| value === 'approved'
|
|
20
|
+
|| value === 'rejected'
|
|
21
|
+
|| value === 'expired') {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return 'pending';
|
|
25
|
+
}
|
|
26
|
+
function serializeContactRequest(requestId, data) {
|
|
27
|
+
if (typeof data.approverId !== 'string' || data.approverId.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (typeof data.requesterId !== 'string' || data.requesterId.length === 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (typeof data.targetId !== 'string' || data.targetId.length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const kindValue = typeof data.kind === 'string' ? data.kind : 'dm';
|
|
37
|
+
if (kindValue !== 'dm' && kindValue !== 'group_invite') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
let groupContext = null;
|
|
41
|
+
if (kindValue === 'group_invite') {
|
|
42
|
+
const ctx = isRecord(data.groupContext) ? data.groupContext : null;
|
|
43
|
+
const conversationId = typeof ctx?.conversationId === 'string'
|
|
44
|
+
? ctx.conversationId.trim()
|
|
45
|
+
: '';
|
|
46
|
+
if (!conversationId) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
groupContext = {
|
|
50
|
+
conversationId,
|
|
51
|
+
groupName: typeof ctx?.groupName === 'string' ? ctx.groupName : null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const payload = {
|
|
55
|
+
id: requestId,
|
|
56
|
+
requesterId: data.requesterId,
|
|
57
|
+
requesterName: typeof data.requesterName === 'string' ? data.requesterName : 'Unknown',
|
|
58
|
+
requesterAvatarUrl: typeof data.requesterAvatarUrl === 'string' ? data.requesterAvatarUrl : null,
|
|
59
|
+
targetId: data.targetId,
|
|
60
|
+
approverId: data.approverId,
|
|
61
|
+
message: typeof data.message === 'string' ? data.message : null,
|
|
62
|
+
status: normalizeStatus(data.status),
|
|
63
|
+
kind: kindValue,
|
|
64
|
+
createdAt: normalizeTimestamp(data.createdAt),
|
|
65
|
+
resolvedAt: normalizeTimestamp(data.resolvedAt),
|
|
66
|
+
expiresAt: normalizeTimestamp(data.expiresAt),
|
|
67
|
+
};
|
|
68
|
+
if (groupContext) {
|
|
69
|
+
payload.groupContext = groupContext;
|
|
70
|
+
}
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./media.js"), exports);
|
|
18
|
+
__exportStar(require("./message.js"), exports);
|
|
19
|
+
__exportStar(require("./turnProtocol.js"), exports);
|
|
20
|
+
__exportStar(require("./agentBehaviorPolicy.js"), exports);
|
|
21
|
+
__exportStar(require("./contactRequest.js"), exports);
|