Repository navigation
feat(provider-muse): show Muse subscription limits in Usage limits #18815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,6 +148,22 @@ export const MuseContextUsage = Schema.Struct({ | |
| usedTokens: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| windowTokens: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))), | ||
| }); | ||
| /** MSP `usage/changed` / `usage/read` payload: the Meta subscription windows last observed. */ | ||
| export const MuseSubscriptionUsage = Schema.Struct({ | ||
| observedAtMs: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| tier: Schema.String, | ||
| weekly: Schema.Struct({ | ||
| resetsAtMs: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| usedPercent: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| }), | ||
| window: Schema.Struct({ | ||
| resetsAtMs: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| usedPercent: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)), | ||
| windowDurationMins: Schema.Finite.check(Schema.isGreaterThan(0)), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Require an integer window duration. The schema accepts 🤖 Prompt for AI Agents |
||
| }), | ||
| }); | ||
| export type MuseSubscriptionUsage = typeof MuseSubscriptionUsage.Type; | ||
|
|
||
| export const MuseTodoList = Schema.Struct({ | ||
| items: Schema.Array(Schema.Struct({ text: Schema.String, status: Schema.String })), | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { describe, expect, it } from "@effect/vitest"; | ||
|
|
||
| import type { MuseSubscriptionUsage } from "./protocol.ts"; | ||
| import { museSubscriptionUsageToLimits, museSubscriptionUsageToUpdate } from "./usageLimits.ts"; | ||
|
|
||
| const NOW_MS = Date.parse("2026-10-12T12:00:00.000Z"); | ||
| const CHECKED_AT = "2026-10-12T12:00:00.000Z"; | ||
|
|
||
| const usage: MuseSubscriptionUsage = { | ||
| observedAtMs: NOW_MS, | ||
| tier: "meta-subscription", | ||
| window: { | ||
| usedPercent: 3, | ||
| resetsAtMs: Date.parse("2026-10-12T14:19:00.000Z"), | ||
| windowDurationMins: 300, | ||
| }, | ||
| weekly: { | ||
| usedPercent: 46, | ||
| resetsAtMs: Date.parse("2026-10-19T00:00:00.000Z"), | ||
| }, | ||
| }; | ||
|
|
||
| describe("Muse subscription usage limits", () => { | ||
| it("maps the session and weekly windows Muse reports", () => { | ||
| expect(museSubscriptionUsageToLimits({ usage, checkedAt: CHECKED_AT, nowMs: NOW_MS })).toEqual({ | ||
| checkedAt: CHECKED_AT, | ||
| windows: [ | ||
| { | ||
| id: "session", | ||
| kind: "session", | ||
| label: "Session", | ||
| usedPercent: 3, | ||
| windowDurationMins: 300, | ||
| resetsAt: "2026-10-12T14:19:00.000Z", | ||
| }, | ||
| { | ||
| id: "weekly", | ||
| kind: "weekly", | ||
| label: "Weekly", | ||
| usedPercent: 46, | ||
| windowDurationMins: 10080, | ||
| resetsAt: "2026-10-19T00:00:00.000Z", | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it("starts a window over empty once its reset has passed", () => { | ||
| const limits = museSubscriptionUsageToLimits({ | ||
| usage: { | ||
| ...usage, | ||
| window: { ...usage.window, usedPercent: 97, resetsAtMs: NOW_MS - 1 }, | ||
| }, | ||
| checkedAt: CHECKED_AT, | ||
| nowMs: NOW_MS, | ||
| }); | ||
| expect(limits.windows[0]).toEqual({ | ||
| id: "session", | ||
| kind: "session", | ||
| label: "Session", | ||
| usedPercent: 0, | ||
| windowDurationMins: 300, | ||
| }); | ||
| expect(limits.windows[1]?.usedPercent).toBe(46); | ||
| }); | ||
|
|
||
| it("clamps over-quota percentages to the contract range and reuses the windows for live updates", () => { | ||
| const update = museSubscriptionUsageToUpdate({ | ||
| usage: { ...usage, weekly: { ...usage.weekly, usedPercent: 130 } }, | ||
| nowMs: NOW_MS, | ||
| }); | ||
| expect(update.windows.map((window) => window.usedPercent)).toEqual([3, 100]); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,94 @@ | ||||||
| /** | ||||||
| * Muse subscription usage. Meta has no usage endpoint T3 can poll: Muse | ||||||
| * reports the signed-in account's windows with each reply and forwards them | ||||||
| * as the MSP `usage/changed` notification (the same payload `usage/read` | ||||||
| * returns, except that a fresh host has observed nothing). The adapter hands | ||||||
| * each report to the driver, which publishes it as the instance's | ||||||
| * `usageLimits` and re-derives the windows from the newest report on every | ||||||
| * status check. A window whose reset has passed starts over empty. | ||||||
| * | ||||||
| * @module server/usageLimits | ||||||
| */ | ||||||
| import type { | ||||||
| ProviderUsageLimitsUpdate, | ||||||
| ServerProviderUsageLimits, | ||||||
| ServerProviderUsageWindow, | ||||||
| } from "@t3tools/contracts"; | ||||||
| import * as DateTime from "effect/DateTime"; | ||||||
| import * as Option from "effect/Option"; | ||||||
|
|
||||||
| import { clampPercent, makeUsageLimits } from "@t3tools/provider-core/server/usageLimits"; | ||||||
| import type { MuseSubscriptionUsage } from "./protocol.ts"; | ||||||
|
|
||||||
| const WEEK_MINS = 7 * 24 * 60; | ||||||
|
|
||||||
| function isoFromEpochMs(value: number): string | undefined { | ||||||
| const dt = DateTime.make(value); | ||||||
| return Option.isSome(dt) ? DateTime.formatIso(dt.value) : undefined; | ||||||
| } | ||||||
|
|
||||||
| function window(input: { | ||||||
| readonly id: string; | ||||||
| readonly kind: ServerProviderUsageWindow["kind"]; | ||||||
| readonly label: string; | ||||||
| readonly usedPercent: number; | ||||||
| readonly resetsAtMs: number; | ||||||
| readonly windowDurationMins: number; | ||||||
| readonly nowMs: number; | ||||||
| }): ServerProviderUsageWindow { | ||||||
| const expired = input.resetsAtMs <= input.nowMs; | ||||||
| const resetsAt = expired ? undefined : isoFromEpochMs(input.resetsAtMs); | ||||||
| return { | ||||||
| id: input.id, | ||||||
| kind: input.kind, | ||||||
| label: input.label, | ||||||
| usedPercent: expired ? 0 : clampPercent(input.usedPercent), | ||||||
| windowDurationMins: input.windowDurationMins, | ||||||
| ...(resetsAt ? { resetsAt } : {}), | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| /** The two subscription windows a Muse report names, as T3 limit windows. */ | ||||||
| export function museSubscriptionUsageWindows( | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Remove the unused export to restore the lint check.
Proposed change--- "a/packages/provider-muse/src/server/usageLimits.ts"
+++ "b/packages/provider-muse/src/server/usageLimits.ts"
@@ -49,7 +49,7 @@
}
/** The two subscription windows a Muse report names, as T3 limit windows. */
-export function museSubscriptionUsageWindows(
+function museSubscriptionUsageWindows(
usage: MuseSubscriptionUsage,
nowMs: number,
): ReadonlyArray<ServerProviderUsageWindow> {📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: CI / 16_Lint.txt[error] 52-52: Command 'vp run knip:check' failed: Knip reports the export 'museSubscriptionUsageWindows' as unused. 🪛 GitHub Actions: CI / Lint[error] 52-52: Command 🤖 Prompt for AI AgentsSources: Coding guidelines, Pipeline failures |
||||||
| usage: MuseSubscriptionUsage, | ||||||
| nowMs: number, | ||||||
| ): ReadonlyArray<ServerProviderUsageWindow> { | ||||||
| return [ | ||||||
| window({ | ||||||
| id: "session", | ||||||
| kind: "session", | ||||||
| label: "Session", | ||||||
| usedPercent: usage.window.usedPercent, | ||||||
| resetsAtMs: usage.window.resetsAtMs, | ||||||
| windowDurationMins: usage.window.windowDurationMins, | ||||||
| nowMs, | ||||||
| }), | ||||||
| window({ | ||||||
| id: "weekly", | ||||||
| kind: "weekly", | ||||||
| label: "Weekly", | ||||||
| usedPercent: usage.weekly.usedPercent, | ||||||
| resetsAtMs: usage.weekly.resetsAtMs, | ||||||
| windowDurationMins: WEEK_MINS, | ||||||
| nowMs, | ||||||
| }), | ||||||
| ]; | ||||||
| } | ||||||
|
|
||||||
| export function museSubscriptionUsageToLimits(input: { | ||||||
| readonly usage: MuseSubscriptionUsage; | ||||||
| readonly checkedAt: string; | ||||||
| readonly nowMs: number; | ||||||
| }): ServerProviderUsageLimits { | ||||||
| return makeUsageLimits({ | ||||||
| checkedAt: input.checkedAt, | ||||||
| windows: museSubscriptionUsageWindows(input.usage, input.nowMs), | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| export function museSubscriptionUsageToUpdate(input: { | ||||||
| readonly usage: MuseSubscriptionUsage; | ||||||
| readonly nowMs: number; | ||||||
| }): ProviderUsageLimitsUpdate { | ||||||
| return { windows: [...museSubscriptionUsageWindows(input.usage, input.nowMs)] }; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium Live updates for expired windows leave the previously published 🤖 Copy this AI Prompt to have your agent fix this: |
||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: pingdotgg/t3code
Length of output: 26039
🏁 Script executed:
Repository: pingdotgg/t3code
Length of output: 22664
🏁 Script executed:
Repository: pingdotgg/t3code
Length of output: 16431
🏁 Script executed:
Repository: pingdotgg/t3code
Length of output: 21163
Retain the newest Muse usage report.
retainedUsageis shared by the driver instance. EachopenSessionowns a scoped Muse host, and each host'susage/changedhandler invokes the sameonSubscriptionUsagecallback. Because the callback unconditionally executesRef.set(retainedUsage, usage), an older report can replace a newer report when host notifications arrive out of order. That older report is then published and reused by later status checks.Compare
usage.observedAtMswith the retained report and ignore older reports. Serialize the comparison, retention, and snapshot publication as one operation so an older callback cannot publish after a newer one.🤖 Prompt for AI Agents