( userId: string, context: StreamingContext, execContext: ExecutionContext, options: OrchestratorOptions )
| 25 | * so the client renders the upgrade prompt. |
| 26 | */ |
| 27 | export async function handleBillingLimitResponse( |
| 28 | userId: string, |
| 29 | context: StreamingContext, |
| 30 | execContext: ExecutionContext, |
| 31 | options: OrchestratorOptions |
| 32 | ): Promise<void> { |
| 33 | let action: 'upgrade_plan' | 'increase_limit' = 'upgrade_plan' |
| 34 | let message = "You've reached your usage limit. Please upgrade your plan to continue." |
| 35 | try { |
| 36 | const sub = await getHighestPrioritySubscription(userId) |
| 37 | if (sub && isPaid(sub.plan)) { |
| 38 | // Paid subs use the existing `increase_limit` action so the UI |
| 39 | // (`UsageUpgradeDisplay`) renders its standard button. The message |
| 40 | // text does the work of clarifying the action when the user can't |
| 41 | // actually self-serve the limit change. |
| 42 | action = 'increase_limit' |
| 43 | const orgScoped = isOrgScopedSubscription(sub, userId) |
| 44 | if (orgScoped) { |
| 45 | message = isEnterprise(sub.plan) |
| 46 | ? "You've reached your organization's usage limit for this billing period. Only an organization admin or Sim support can raise an enterprise limit — reach out to them to continue." |
| 47 | : "You've reached your organization's usage limit for this billing period. Only an organization owner or admin can raise the limit — please ask them to update it from the team billing settings." |
| 48 | } else { |
| 49 | message = |
| 50 | "You've reached your usage limit for this billing period. Please increase your usage limit from billing settings to continue." |
| 51 | } |
| 52 | } |
| 53 | } catch { |
| 54 | logger.warn('Failed to determine subscription plan, defaulting to upgrade_plan') |
| 55 | } |
| 56 | |
| 57 | const upgradePayload = JSON.stringify({ |
| 58 | reason: 'usage_limit', |
| 59 | action, |
| 60 | message, |
| 61 | }) |
| 62 | const syntheticContent = `<usage_upgrade>${upgradePayload}</usage_upgrade>` |
| 63 | |
| 64 | const syntheticEvents: StreamEvent[] = [ |
| 65 | { |
| 66 | type: MothershipStreamV1EventType.text, |
| 67 | payload: { |
| 68 | channel: MothershipStreamV1TextChannel.assistant, |
| 69 | text: syntheticContent, |
| 70 | }, |
| 71 | }, |
| 72 | { |
| 73 | type: MothershipStreamV1EventType.complete, |
| 74 | payload: { |
| 75 | status: MothershipStreamV1CompletionStatus.complete, |
| 76 | }, |
| 77 | }, |
| 78 | ] |
| 79 | |
| 80 | for (const event of syntheticEvents) { |
| 81 | try { |
| 82 | await options.onEvent?.(event) |
| 83 | } catch { |
| 84 | logger.warn('Failed to forward synthetic billing event', { type: event.type }) |
no test coverage detected