@ciromaciel/contract 1.0.3 → 1.0.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/package.json +1 -1
- package/src/plan/notice.js +17 -38
package/package.json
CHANGED
package/src/plan/notice.js
CHANGED
|
@@ -22,66 +22,45 @@
|
|
|
22
22
|
* @param {object} cfg Where the secret and the Payments URL live.
|
|
23
23
|
* @param {string} ownerId
|
|
24
24
|
* @param {object} [copy] Product wording. `product` is the name a person reads
|
|
25
|
-
* ("
|
|
25
|
+
* ("Infrastructure Auth"), `keeps` is what goes on working,
|
|
26
26
|
* `frozen` is what does not.
|
|
27
|
-
* @
|
|
27
|
+
* @param {number} [current] How many resources the owner has now. Without it,
|
|
28
|
+
* an owner with no plan gets no notice.
|
|
29
|
+
* @returns {Promise<{state: 'active'|'free'|'none'|'unknown', plan: string|null,
|
|
28
30
|
* limit: number|null, message: string|null}>}
|
|
29
31
|
*/
|
|
30
|
-
export async function getPlanNotice(gate, cfg, ownerId, copy = {}) {
|
|
32
|
+
export async function getPlanNotice(gate, cfg, ownerId, copy = {}, current = null) {
|
|
31
33
|
const ent = await gate.getEntitlements(cfg, ownerId)
|
|
32
|
-
|
|
33
|
-
// `Infinity` does not survive JSON.stringify — it becomes `null` silently.
|
|
34
|
-
// Converting on purpose is what keeps the wire honest.
|
|
35
34
|
const limit = ent.limit === Infinity ? null : ent.limit
|
|
36
|
-
|
|
37
|
-
// Could not ask: say nothing. Dunning somebody because our OWN billing call
|
|
38
|
-
// timed out is worse than staying quiet for the length of an outage.
|
|
39
35
|
if (ent.unknown) return { state: 'unknown', plan: null, limit: null, message: null }
|
|
40
|
-
|
|
41
36
|
if (ent.subscribed) return { state: 'active', plan: ent.plan, limit, message: null }
|
|
42
|
-
|
|
43
37
|
/*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* opens with the refusal makes them assume the worst.
|
|
51
|
-
*
|
|
52
|
-
* Portuguese, because this is the one string here a person reads.
|
|
38
|
+
* NO PLAN IS NOT A PROBLEM TO REPORT. Since the free floor became one
|
|
39
|
+
* resource (2026-09-17), an owner with no plan is simply on the free tier,
|
|
40
|
+
* and telling them "your subscription is not active" reads as a billing
|
|
41
|
+
* failure they never had. The notice speaks only when the free tier is
|
|
42
|
+
* actually full — and it can only know that from `current`, so without it
|
|
43
|
+
* there is no notice: the refusal on creation already says what to do.
|
|
53
44
|
*/
|
|
45
|
+
if (typeof current !== 'number' || limit === null || current < limit) {
|
|
46
|
+
return { state: 'free', plan: ent.plan, limit, message: null }
|
|
47
|
+
}
|
|
54
48
|
const product = copy.product || 'este produto'
|
|
55
49
|
const keeps = copy.keeps || 'O que você já criou continua funcionando'
|
|
56
50
|
const frozen = copy.frozen || 'apenas a criação de novos recursos está bloqueada'
|
|
57
|
-
|
|
58
51
|
return {
|
|
59
52
|
state: 'none',
|
|
60
53
|
plan: ent.plan,
|
|
61
54
|
limit,
|
|
62
|
-
message: `
|
|
55
|
+
message: `O plano gratuito do ${product} chegou ao limite. ${keeps} — ${frozen}. Assine para criar mais.`,
|
|
63
56
|
}
|
|
64
57
|
}
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
* Attaches the notice to an MCP tool result, without ever refusing it.
|
|
68
|
-
*
|
|
69
|
-
* IT TRAVELS AS A FIELD, NOT AS PROSE. The stack's MCP rule is that
|
|
70
|
-
* `content[0].text` is the `JSON.stringify` of the same object in
|
|
71
|
-
* `structuredContent`. Appending a second text block would break that mirror,
|
|
72
|
-
* and a client reading one field would disagree with one reading the other.
|
|
73
|
-
*
|
|
74
|
-
* ERRORS ARE LEFT ALONE: an `isError` result already carries the reason the
|
|
75
|
-
* agent needs, and billing copy on top of a permission failure buries it.
|
|
76
|
-
*
|
|
77
|
-
* AND IT NEVER BREAKS A TOOL CALL — the notice is decoration, so an unreachable
|
|
78
|
-
* Payments or an outright throw both leave the result untouched.
|
|
79
|
-
*/
|
|
80
|
-
export async function withPlanNotice(result, gate, cfg, ownerId, copy) {
|
|
59
|
+
export async function withPlanNotice(result, gate, cfg, ownerId, copy, current = null) {
|
|
81
60
|
if (!result || result.isError || !result.structuredContent) return result
|
|
82
61
|
|
|
83
62
|
try {
|
|
84
|
-
const notice = await getPlanNotice(gate, cfg, ownerId, copy)
|
|
63
|
+
const notice = await getPlanNotice(gate, cfg, ownerId, copy, current)
|
|
85
64
|
if (notice.state !== 'none') return result
|
|
86
65
|
|
|
87
66
|
const payload = { ...result.structuredContent, planNotice: notice }
|