@advicenxt/sbp-server 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.
Files changed (63) hide show
  1. package/benchmarks/bench.ts +272 -0
  2. package/dist/auth.d.ts +20 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +69 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/blackboard.d.ts +84 -0
  7. package/dist/blackboard.d.ts.map +1 -0
  8. package/dist/blackboard.js +502 -0
  9. package/dist/blackboard.js.map +1 -0
  10. package/dist/cli.d.ts +7 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +102 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/conditions.d.ts +27 -0
  15. package/dist/conditions.d.ts.map +1 -0
  16. package/dist/conditions.js +240 -0
  17. package/dist/conditions.js.map +1 -0
  18. package/dist/decay.d.ts +21 -0
  19. package/dist/decay.d.ts.map +1 -0
  20. package/dist/decay.js +88 -0
  21. package/dist/decay.js.map +1 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +13 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/rate-limiter.d.ts +21 -0
  27. package/dist/rate-limiter.d.ts.map +1 -0
  28. package/dist/rate-limiter.js +75 -0
  29. package/dist/rate-limiter.js.map +1 -0
  30. package/dist/server.d.ts +63 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +401 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/store.d.ts +54 -0
  35. package/dist/store.d.ts.map +1 -0
  36. package/dist/store.js +55 -0
  37. package/dist/store.js.map +1 -0
  38. package/dist/types.d.ts +247 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +26 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/validation.d.ts +296 -0
  43. package/dist/validation.d.ts.map +1 -0
  44. package/dist/validation.js +205 -0
  45. package/dist/validation.js.map +1 -0
  46. package/eslint.config.js +26 -0
  47. package/package.json +66 -0
  48. package/src/auth.ts +89 -0
  49. package/src/blackboard.test.ts +287 -0
  50. package/src/blackboard.ts +651 -0
  51. package/src/cli.ts +116 -0
  52. package/src/conditions.ts +305 -0
  53. package/src/conformance.test.ts +686 -0
  54. package/src/decay.ts +103 -0
  55. package/src/index.ts +24 -0
  56. package/src/rate-limiter.ts +104 -0
  57. package/src/server.integration.test.ts +436 -0
  58. package/src/server.ts +500 -0
  59. package/src/store.ts +108 -0
  60. package/src/types.ts +314 -0
  61. package/src/validation.ts +251 -0
  62. package/tsconfig.eslint.json +5 -0
  63. package/tsconfig.json +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,cAAc,EAMf,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,cAAc,EACzB,GAAG,EAAE,iBAAiB,GACrB,gBAAgB,CAalB;AAoPD;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAUnF"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Scent condition evaluation
3
+ */
4
+ import { computeIntensity, isEvaporated } from "./decay.js";
5
+ /**
6
+ * Evaluate a scent condition against the current environment
7
+ */
8
+ export function evaluateCondition(condition, ctx) {
9
+ switch (condition.type) {
10
+ case "threshold":
11
+ return evaluateThreshold(condition, ctx);
12
+ case "composite":
13
+ return evaluateComposite(condition, ctx);
14
+ case "rate":
15
+ return evaluateRate(condition, ctx);
16
+ case "pattern":
17
+ return evaluatePattern(condition, ctx);
18
+ default:
19
+ return { met: false, value: 0, matchingPheromoneIds: [] };
20
+ }
21
+ }
22
+ /**
23
+ * Evaluate a threshold condition
24
+ */
25
+ function evaluateThreshold(condition, ctx) {
26
+ const { pheromones, now } = ctx;
27
+ // Filter matching pheromones
28
+ const matching = pheromones.filter((p) => {
29
+ // Trail must match
30
+ if (p.trail !== condition.trail)
31
+ return false;
32
+ // Type must match (or wildcard)
33
+ if (condition.signal_type !== "*" && p.type !== condition.signal_type)
34
+ return false;
35
+ // Must not be evaporated
36
+ if (isEvaporated(p, now))
37
+ return false;
38
+ // Tag filter
39
+ if (condition.tags && !matchTags(p.tags, condition.tags))
40
+ return false;
41
+ return true;
42
+ });
43
+ // Compute aggregate value
44
+ let aggValue;
45
+ const intensities = matching.map((p) => computeIntensity(p, now));
46
+ switch (condition.aggregation) {
47
+ case "sum":
48
+ aggValue = intensities.reduce((a, b) => a + b, 0);
49
+ break;
50
+ case "max":
51
+ aggValue = intensities.length > 0 ? Math.max(...intensities) : 0;
52
+ break;
53
+ case "avg":
54
+ aggValue = intensities.length > 0
55
+ ? intensities.reduce((a, b) => a + b, 0) / intensities.length
56
+ : 0;
57
+ break;
58
+ case "count":
59
+ aggValue = matching.length;
60
+ break;
61
+ case "any":
62
+ aggValue = matching.length > 0 ? 1 : 0;
63
+ break;
64
+ default:
65
+ aggValue = 0;
66
+ }
67
+ // Compare
68
+ const met = compare(aggValue, condition.operator, condition.value);
69
+ return {
70
+ met,
71
+ value: aggValue,
72
+ matchingPheromoneIds: matching.map((p) => p.id),
73
+ };
74
+ }
75
+ /**
76
+ * Evaluate a composite condition (AND, OR, NOT)
77
+ */
78
+ function evaluateComposite(condition, ctx) {
79
+ if (condition.conditions.length === 0) {
80
+ return { met: false, value: 0, matchingPheromoneIds: [] };
81
+ }
82
+ const results = condition.conditions.map((c) => evaluateCondition(c, ctx));
83
+ const allPheromoneIds = [...new Set(results.flatMap((r) => r.matchingPheromoneIds))];
84
+ let met;
85
+ switch (condition.operator) {
86
+ case "and":
87
+ met = results.every((r) => r.met);
88
+ break;
89
+ case "or":
90
+ met = results.some((r) => r.met);
91
+ break;
92
+ case "not":
93
+ met = !results[0].met;
94
+ break;
95
+ default:
96
+ met = false;
97
+ }
98
+ return {
99
+ met,
100
+ value: results.filter((r) => r.met).length,
101
+ matchingPheromoneIds: allPheromoneIds,
102
+ };
103
+ }
104
+ /**
105
+ * Evaluate a rate condition
106
+ */
107
+ function evaluateRate(condition, ctx) {
108
+ const { emissionHistory = [], now } = ctx;
109
+ // Filter emissions in the window
110
+ const windowStart = now - condition.window_ms;
111
+ const relevantEmissions = emissionHistory.filter((e) => e.trail === condition.trail &&
112
+ (condition.signal_type === "*" || e.type === condition.signal_type) &&
113
+ e.timestamp >= windowStart);
114
+ let value;
115
+ if (condition.metric === "emissions_per_second") {
116
+ const windowSeconds = condition.window_ms / 1000;
117
+ value = relevantEmissions.length / windowSeconds;
118
+ }
119
+ else {
120
+ // intensity_delta would require tracking intensity over time
121
+ // For now, approximate with emission count
122
+ value = relevantEmissions.length;
123
+ }
124
+ const met = compare(value, condition.operator, condition.value);
125
+ return {
126
+ met,
127
+ value,
128
+ matchingPheromoneIds: [],
129
+ };
130
+ }
131
+ /**
132
+ * Evaluate a pattern condition
133
+ * Checks if a sequence of pheromone emissions occurred within a time window
134
+ */
135
+ function evaluatePattern(condition, ctx) {
136
+ const { emissionHistory = [], now } = ctx;
137
+ const { sequence, window_ms, ordered = true } = condition;
138
+ // Filter emissions within the window
139
+ const windowStart = now - window_ms;
140
+ const relevant = emissionHistory.filter((e) => e.timestamp >= windowStart);
141
+ if (relevant.length === 0 || sequence.length === 0) {
142
+ return { met: false, value: 0, matchingPheromoneIds: [] };
143
+ }
144
+ if (ordered) {
145
+ // Ordered: each step must appear after the previous one
146
+ let searchFrom = 0;
147
+ let matchCount = 0;
148
+ for (const step of sequence) {
149
+ let found = false;
150
+ for (let i = searchFrom; i < relevant.length; i++) {
151
+ const emission = relevant[i];
152
+ if (emission.trail === step.trail &&
153
+ emission.type === step.signal_type) {
154
+ found = true;
155
+ searchFrom = i + 1;
156
+ matchCount++;
157
+ break;
158
+ }
159
+ }
160
+ if (!found)
161
+ break;
162
+ }
163
+ return {
164
+ met: matchCount === sequence.length,
165
+ value: matchCount / sequence.length,
166
+ matchingPheromoneIds: [],
167
+ };
168
+ }
169
+ else {
170
+ // Unordered: all steps must appear in any order
171
+ const remaining = [...relevant];
172
+ let matchCount = 0;
173
+ for (const step of sequence) {
174
+ const idx = remaining.findIndex((e) => e.trail === step.trail && e.type === step.signal_type);
175
+ if (idx >= 0) {
176
+ remaining.splice(idx, 1);
177
+ matchCount++;
178
+ }
179
+ }
180
+ return {
181
+ met: matchCount === sequence.length,
182
+ value: matchCount / sequence.length,
183
+ matchingPheromoneIds: [],
184
+ };
185
+ }
186
+ }
187
+ /**
188
+ * Match tags against a filter
189
+ */
190
+ function matchTags(tags, filter) {
191
+ if (filter.any && filter.any.length > 0) {
192
+ if (!filter.any.some((t) => tags.includes(t)))
193
+ return false;
194
+ }
195
+ if (filter.all && filter.all.length > 0) {
196
+ if (!filter.all.every((t) => tags.includes(t)))
197
+ return false;
198
+ }
199
+ if (filter.none && filter.none.length > 0) {
200
+ if (filter.none.some((t) => tags.includes(t)))
201
+ return false;
202
+ }
203
+ return true;
204
+ }
205
+ /**
206
+ * Compare two values with an operator
207
+ */
208
+ function compare(a, op, b) {
209
+ switch (op) {
210
+ case ">=":
211
+ return a >= b;
212
+ case ">":
213
+ return a > b;
214
+ case "<=":
215
+ return a <= b;
216
+ case "<":
217
+ return a < b;
218
+ case "==":
219
+ return a === b;
220
+ case "!=":
221
+ return a !== b;
222
+ default:
223
+ return false;
224
+ }
225
+ }
226
+ /**
227
+ * Create a snapshot of a pheromone for trigger payloads
228
+ */
229
+ export function createSnapshot(pheromone, now) {
230
+ return {
231
+ id: pheromone.id,
232
+ trail: pheromone.trail,
233
+ type: pheromone.type,
234
+ current_intensity: computeIntensity(pheromone, now),
235
+ payload: pheromone.payload,
236
+ age_ms: now - pheromone.emitted_at,
237
+ tags: pheromone.tags,
238
+ };
239
+ }
240
+ //# sourceMappingURL=conditions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.js","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Decay computation utilities
3
+ */
4
+ import type { DecayModel, Pheromone } from "./types.js";
5
+ /**
6
+ * Compute the current intensity of a pheromone after decay
7
+ */
8
+ export declare function computeIntensity(pheromone: Pheromone, now: number): number;
9
+ /**
10
+ * Check if a pheromone has evaporated below its floor
11
+ */
12
+ export declare function isEvaporated(pheromone: Pheromone, now: number): boolean;
13
+ /**
14
+ * Get the default decay model
15
+ */
16
+ export declare function defaultDecay(): DecayModel;
17
+ /**
18
+ * Estimate time until pheromone evaporates
19
+ */
20
+ export declare function timeToEvaporation(pheromone: Pheromone, now: number): number | null;
21
+ //# sourceMappingURL=decay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decay.d.ts","sourceRoot":"","sources":["../src/decay.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAmC1E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAEzC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuClF"}
package/dist/decay.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Decay computation utilities
3
+ */
4
+ /**
5
+ * Compute the current intensity of a pheromone after decay
6
+ */
7
+ export function computeIntensity(pheromone, now) {
8
+ const elapsed = now - pheromone.last_reinforced_at;
9
+ if (elapsed <= 0) {
10
+ return pheromone.initial_intensity;
11
+ }
12
+ switch (pheromone.decay_model.type) {
13
+ case "exponential": {
14
+ const halfLife = pheromone.decay_model.half_life_ms;
15
+ return pheromone.initial_intensity * Math.pow(0.5, elapsed / halfLife);
16
+ }
17
+ case "linear": {
18
+ const rate = pheromone.decay_model.rate_per_ms;
19
+ return Math.max(0, pheromone.initial_intensity - rate * elapsed);
20
+ }
21
+ case "step": {
22
+ const steps = pheromone.decay_model.steps;
23
+ // Find the applicable step (steps should be sorted by at_ms)
24
+ for (let i = steps.length - 1; i >= 0; i--) {
25
+ if (elapsed >= steps[i].at_ms) {
26
+ return steps[i].intensity;
27
+ }
28
+ }
29
+ return pheromone.initial_intensity;
30
+ }
31
+ case "immortal":
32
+ return pheromone.initial_intensity;
33
+ default:
34
+ return 0;
35
+ }
36
+ }
37
+ /**
38
+ * Check if a pheromone has evaporated below its floor
39
+ */
40
+ export function isEvaporated(pheromone, now) {
41
+ return computeIntensity(pheromone, now) < pheromone.ttl_floor;
42
+ }
43
+ /**
44
+ * Get the default decay model
45
+ */
46
+ export function defaultDecay() {
47
+ return { type: "exponential", half_life_ms: 300000 }; // 5 minutes
48
+ }
49
+ /**
50
+ * Estimate time until pheromone evaporates
51
+ */
52
+ export function timeToEvaporation(pheromone, now) {
53
+ const currentIntensity = computeIntensity(pheromone, now);
54
+ if (currentIntensity <= pheromone.ttl_floor) {
55
+ return 0; // Already evaporated
56
+ }
57
+ switch (pheromone.decay_model.type) {
58
+ case "exponential": {
59
+ // Solve: ttl_floor = current * 0.5^(t/halfLife)
60
+ // t = halfLife * log2(current / ttl_floor)
61
+ const halfLife = pheromone.decay_model.half_life_ms;
62
+ const ratio = currentIntensity / pheromone.ttl_floor;
63
+ return halfLife * Math.log2(ratio);
64
+ }
65
+ case "linear": {
66
+ // Solve: ttl_floor = current - rate * t
67
+ // t = (current - ttl_floor) / rate
68
+ const rate = pheromone.decay_model.rate_per_ms;
69
+ if (rate <= 0)
70
+ return null;
71
+ return (currentIntensity - pheromone.ttl_floor) / rate;
72
+ }
73
+ case "step": {
74
+ // Find next step below threshold
75
+ const steps = pheromone.decay_model.steps;
76
+ const elapsed = now - pheromone.last_reinforced_at;
77
+ for (const step of steps) {
78
+ if (step.at_ms > elapsed && step.intensity < pheromone.ttl_floor) {
79
+ return step.at_ms - elapsed;
80
+ }
81
+ }
82
+ return null; // May never evaporate with given steps
83
+ }
84
+ case "immortal":
85
+ return null; // Never evaporates
86
+ }
87
+ }
88
+ //# sourceMappingURL=decay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decay.js","sourceRoot":"","sources":["../src/decay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SBP Server - Public API
3
+ */
4
+ export { Blackboard, type BlackboardOptions, type TriggerHandler } from "./blackboard.js";
5
+ export { SbpServer, type ServerOptions } from "./server.js";
6
+ export { type PheromoneStore, MemoryStore, createStore, type StoreType } from "./store.js";
7
+ export * from "./types.js";
8
+ export { computeIntensity, isEvaporated, defaultDecay, timeToEvaporation } from "./decay.js";
9
+ export { evaluateCondition, createSnapshot } from "./conditions.js";
10
+ export { createAuthHook, type AuthOptions } from "./auth.js";
11
+ export { createRateLimitHook, type RateLimitOptions } from "./rate-limiter.js";
12
+ export { validateEnvelope, validateParams, EmitParamsSchema, SniffParamsSchema, RegisterScentParamsSchema, DeregisterScentParamsSchema, EvaporateParamsSchema, InspectParamsSchema, JsonRpcRequestSchema, } from "./validation.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,KAAK,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAC3F,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,2BAA2B,EAC3B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,GACvB,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SBP Server - Public API
3
+ */
4
+ export { Blackboard } from "./blackboard.js";
5
+ export { SbpServer } from "./server.js";
6
+ export { MemoryStore, createStore } from "./store.js";
7
+ export * from "./types.js";
8
+ export { computeIntensity, isEvaporated, defaultDecay, timeToEvaporation } from "./decay.js";
9
+ export { evaluateCondition, createSnapshot } from "./conditions.js";
10
+ export { createAuthHook } from "./auth.js";
11
+ export { createRateLimitHook } from "./rate-limiter.js";
12
+ export { validateEnvelope, validateParams, EmitParamsSchema, SniffParamsSchema, RegisterScentParamsSchema, DeregisterScentParamsSchema, EvaporateParamsSchema, InspectParamsSchema, JsonRpcRequestSchema, } from "./validation.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAA+C,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAsB,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAuB,WAAW,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAC3F,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAoB,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAyB,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACH,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,2BAA2B,EAC3B,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,GACvB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SBP Rate Limiting Middleware
3
+ * Token bucket rate limiter per agent ID
4
+ */
5
+ import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction } from "fastify";
6
+ export interface RateLimitOptions {
7
+ /** Maximum requests per window (default: 1000) */
8
+ maxRequests?: number;
9
+ /** Window duration in milliseconds (default: 60000 = 1 minute) */
10
+ windowMs?: number;
11
+ /** Maximum scent registrations per agent (default: 100) */
12
+ maxScentRegistrations?: number;
13
+ }
14
+ /**
15
+ * Create a Fastify onRequest hook for rate limiting.
16
+ *
17
+ * Uses a token bucket algorithm per agent ID (from `Sbp-Agent-Id` header).
18
+ * Returns JSON-RPC error code -32004 (RATE_LIMITED) when the limit is exceeded.
19
+ */
20
+ export declare function createRateLimitHook(options?: RateLimitOptions): (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => void;
21
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAErF,MAAM,WAAW,gBAAgB;IAC7B,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAClC;AAOD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,gBAAqB,IAqB1D,SAAS,cAAc,EACvB,OAAO,YAAY,EACnB,MAAM,uBAAuB,KAC9B,IAAI,CAoDV"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SBP Rate Limiting Middleware
3
+ * Token bucket rate limiter per agent ID
4
+ */
5
+ /**
6
+ * Create a Fastify onRequest hook for rate limiting.
7
+ *
8
+ * Uses a token bucket algorithm per agent ID (from `Sbp-Agent-Id` header).
9
+ * Returns JSON-RPC error code -32004 (RATE_LIMITED) when the limit is exceeded.
10
+ */
11
+ export function createRateLimitHook(options = {}) {
12
+ const maxRequests = options.maxRequests ?? 1000;
13
+ const windowMs = options.windowMs ?? 60000;
14
+ const buckets = new Map();
15
+ // Periodic cleanup of stale buckets (every 5 minutes)
16
+ const cleanupInterval = setInterval(() => {
17
+ const now = Date.now();
18
+ for (const [key, bucket] of buckets.entries()) {
19
+ if (now - bucket.lastRefill > windowMs * 5) {
20
+ buckets.delete(key);
21
+ }
22
+ }
23
+ }, 300000);
24
+ // Allow GC to clean up the interval if the server is stopped
25
+ if (cleanupInterval.unref) {
26
+ cleanupInterval.unref();
27
+ }
28
+ return function rateLimitHook(request, reply, done) {
29
+ // Skip rate limiting for health checks and OPTIONS
30
+ const url = request.url.split("?")[0];
31
+ if (url === "/health" || request.method === "OPTIONS") {
32
+ done();
33
+ return;
34
+ }
35
+ // Identify the agent
36
+ const agentId = request.headers["sbp-agent-id"] || request.ip || "anonymous";
37
+ // Get or create token bucket
38
+ const now = Date.now();
39
+ let bucket = buckets.get(agentId);
40
+ if (!bucket) {
41
+ bucket = { tokens: maxRequests, lastRefill: now };
42
+ buckets.set(agentId, bucket);
43
+ }
44
+ // Refill tokens based on elapsed time
45
+ const elapsed = now - bucket.lastRefill;
46
+ const refillRate = maxRequests / windowMs;
47
+ bucket.tokens = Math.min(maxRequests, bucket.tokens + elapsed * refillRate);
48
+ bucket.lastRefill = now;
49
+ // Check if request is allowed
50
+ if (bucket.tokens < 1) {
51
+ const retryAfterMs = Math.ceil((1 - bucket.tokens) / refillRate);
52
+ reply
53
+ .status(429)
54
+ .header("Retry-After", Math.ceil(retryAfterMs / 1000).toString())
55
+ .send({
56
+ jsonrpc: "2.0",
57
+ id: null,
58
+ error: {
59
+ code: -32004,
60
+ message: "Rate limited: Too many requests",
61
+ data: {
62
+ retry_after_ms: retryAfterMs,
63
+ limit: maxRequests,
64
+ window_ms: windowMs,
65
+ },
66
+ },
67
+ });
68
+ return;
69
+ }
70
+ // Consume a token
71
+ bucket.tokens -= 1;
72
+ done();
73
+ };
74
+ }
75
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * SBP HTTP Server
3
+ * Streamable HTTP with SSE (following MCP transport patterns)
4
+ */
5
+ import { Blackboard, BlackboardOptions } from "./blackboard.js";
6
+ import { type AuthOptions } from "./auth.js";
7
+ import { type RateLimitOptions } from "./rate-limiter.js";
8
+ export interface ServerOptions extends BlackboardOptions {
9
+ /** HTTP port (default: 3000) */
10
+ port?: number;
11
+ /** Host to bind to (default: localhost) */
12
+ host?: string;
13
+ /** Enable CORS (default: true) */
14
+ cors?: boolean;
15
+ /** Request logging (default: false) */
16
+ logging?: boolean;
17
+ /** Authentication options */
18
+ auth?: AuthOptions;
19
+ /** Rate limiting options */
20
+ rateLimit?: RateLimitOptions;
21
+ }
22
+ export declare class SbpServer {
23
+ private app;
24
+ readonly blackboard: Blackboard;
25
+ private options;
26
+ private sseClients;
27
+ private sessions;
28
+ private eventCounter;
29
+ constructor(options?: ServerOptions);
30
+ private setupRoutes;
31
+ /**
32
+ * Handle POST requests (client -> server messages)
33
+ */
34
+ private handlePost;
35
+ /**
36
+ * Handle GET requests - open SSE stream for triggers
37
+ */
38
+ private handleSSE;
39
+ /**
40
+ * Send an SSE event to a client
41
+ */
42
+ private sendSSEEvent;
43
+ /**
44
+ * Send a JSON-RPC notification via SSE
45
+ */
46
+ private sendSSENotification;
47
+ /**
48
+ * Handle JSON-RPC requests
49
+ */
50
+ private handleRpc;
51
+ /**
52
+ * Set up trigger forwarding to SSE clients
53
+ */
54
+ private setupSSETrigger;
55
+ /**
56
+ * Set up webhook trigger delivery to an agent endpoint
57
+ */
58
+ private setupWebhookTrigger;
59
+ start(): Promise<void>;
60
+ stop(): Promise<void>;
61
+ get address(): string;
62
+ }
63
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAchE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAUD,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAkB;IAC7B,SAAgB,UAAU,EAAE,UAAU,CAAC;IACvC,OAAO,CAAC,OAAO,CAGb;IACF,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,QAAQ,CAA6D;IAC7E,OAAO,CAAC,YAAY,CAAK;gBAEb,OAAO,GAAE,aAAkB;IAsBvC,OAAO,CAAC,WAAW;IAoFnB;;OAEG;YACW,UAAU;IAmDxB;;OAEG;YACW,SAAS;IAuDvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;YACW,SAAS;IAuGvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqCrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAe3B,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF"}