mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
refactor(provider-usage): simplify usage loading
This commit is contained in:
@@ -69,30 +69,9 @@ const minimax: Adapter = {
|
||||
cachePrefixes: ["minimax-direct-"],
|
||||
async run(ctx) {
|
||||
const items = await direct(ctx.candidates, ctx.fetch, ctx.source)
|
||||
const failed = ctx.failedCandidates.flatMap((providerID) =>
|
||||
ctx.preserve(`minimax-direct-${bindings[providerID].region}`),
|
||||
)
|
||||
const skipped = new Set<string>()
|
||||
if (ctx.failedCandidates.length) {
|
||||
const shared = ctx.preserve("minimax-direct-shared")
|
||||
const match = ctx.candidates.find(
|
||||
(candidate) => ctx.preserve("minimax-direct-shared", fingerprint(candidate.key)).length,
|
||||
)
|
||||
const group = match ? ctx.candidates.filter((candidate) => candidate.key === match.key) : []
|
||||
const id = match
|
||||
? group.length > 1
|
||||
? "minimax-direct-shared"
|
||||
: `minimax-direct-${bindings[match.providerID].region}`
|
||||
: undefined
|
||||
const item = items.find((candidate) => candidate.id === id)
|
||||
if (!item || item.fetchState === "unavailable") {
|
||||
failed.push(...shared)
|
||||
if (id) skipped.add(id)
|
||||
}
|
||||
}
|
||||
const merged = [
|
||||
...new Map([...failed, ...items.filter((item) => !skipped.has(item.id))].map((item) => [item.id, item])).values(),
|
||||
]
|
||||
// A candidate is either live or failed, never both, so the id sets are disjoint.
|
||||
const stale = ctx.failedCandidates.flatMap((id) => ctx.preserve(`minimax-direct-${bindings[id].region}`))
|
||||
const merged = [...items, ...stale]
|
||||
ctx.prune(
|
||||
"minimax-direct-",
|
||||
merged.map((item) => item.id),
|
||||
@@ -371,6 +350,9 @@ function makeService(
|
||||
const results = yield* Effect.promise(() =>
|
||||
Promise.all(
|
||||
registry.map((adapter) =>
|
||||
// Adapters are expected to be total (they absorb their own failures into
|
||||
// unavailable/stale snapshots). This catch is the containment boundary so a
|
||||
// faulty future adapter degrades to stale output instead of failing the endpoint.
|
||||
adapter.run(ctx).catch(
|
||||
(): AdapterResult => ({
|
||||
items: adapter.cachePrefixes.flatMap((prefix) =>
|
||||
|
||||
@@ -70,26 +70,18 @@ function durationMs(period: CodingPlanQuotaWindow["period"]) {
|
||||
return period.value * multipliers[period.unit]
|
||||
}
|
||||
|
||||
function periodLabel(period: CodingPlanQuotaWindow["period"]) {
|
||||
const singular = period.value === 1
|
||||
if (period.unit === "hour") return `${period.value}-hour quota`
|
||||
if (period.unit === "day") return singular ? "Daily quota" : `${period.value}-day quota`
|
||||
if (period.unit === "week") return singular ? "Weekly quota" : `${period.value}-week quota`
|
||||
return singular ? "Monthly quota" : `${period.value}-month quota`
|
||||
}
|
||||
|
||||
function window(subscriptionId: string, value: CodingPlanQuotaWindow): ProviderUsage.UsageWindow {
|
||||
const remaining = value.remainingPercent
|
||||
const duration = durationMs(value.period)
|
||||
return {
|
||||
id: `${subscriptionId}:${value.id}`,
|
||||
label: periodLabel(value.period),
|
||||
resource: "subscription",
|
||||
unit: "percent",
|
||||
orientation: "remaining_percent",
|
||||
used: Math.max(0, 100 - remaining),
|
||||
remaining,
|
||||
limit: 100,
|
||||
period: value.period,
|
||||
...(duration !== undefined ? { durationMs: duration } : {}),
|
||||
resetAt: value.resetsAt,
|
||||
state: remaining <= 0 ? "exhausted" : "active",
|
||||
|
||||
@@ -105,11 +105,17 @@ function duration(start: number | undefined, end: number | undefined) {
|
||||
return end - start
|
||||
}
|
||||
|
||||
function label(resource: string, kind: "interval" | "weekly", value: number | undefined) {
|
||||
const prefix = resource === "general" ? "Shared quota" : resource === "video" ? "Video" : resource
|
||||
if (value === 300 * 60 * 1000) return `${prefix} 5-hour`
|
||||
if (value === 10_080 * 60 * 1000) return `${prefix} weekly`
|
||||
return kind === "weekly" ? `${prefix} weekly` : `${prefix} interval`
|
||||
const hourMs = 60 * 60 * 1000
|
||||
const dayMs = 24 * hourMs
|
||||
const weekMs = 7 * dayMs
|
||||
|
||||
function cadence(kind: "interval" | "weekly", span: number | undefined): ProviderUsage.UsagePeriod | undefined {
|
||||
if (kind === "weekly") return { unit: "week", value: 1 }
|
||||
if (span === undefined) return undefined
|
||||
if (span % weekMs === 0) return { unit: "week", value: span / weekMs }
|
||||
if (span % dayMs === 0) return { unit: "day", value: span / dayMs }
|
||||
if (span % hourMs === 0) return { unit: "hour", value: span / hourMs }
|
||||
return undefined
|
||||
}
|
||||
|
||||
function window(
|
||||
@@ -131,8 +137,8 @@ function window(
|
||||
const span = duration(start, end)
|
||||
const base = {
|
||||
id: `${row.model_name}-${kind}`,
|
||||
label: label(row.model_name, kind, span),
|
||||
resource: row.model_name,
|
||||
period: cadence(kind, span),
|
||||
durationMs: span,
|
||||
resetAt: reset(end, remains, fetchedAt),
|
||||
}
|
||||
@@ -245,53 +251,33 @@ export async function direct(
|
||||
identity?: string,
|
||||
) => Promise<ProviderUsage.UsageSnapshot> = (_id, load) => load(),
|
||||
) {
|
||||
const groups = new Map<string, Candidate[]>()
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate.key.startsWith("sk-cp")) continue
|
||||
const group = groups.get(candidate.key) ?? []
|
||||
group.push(candidate)
|
||||
groups.set(candidate.key, group)
|
||||
}
|
||||
|
||||
// Each configured provider is an independent plan with a stable per-region
|
||||
// cache cell, even when providers share a credential.
|
||||
return Promise.all(
|
||||
[...groups.values()].map(async (group) => {
|
||||
const shared = group.length > 1
|
||||
const first = group[0]
|
||||
const id = shared ? "minimax-direct-shared" : `minimax-direct-${bindings[first.providerID].region}`
|
||||
// The key fingerprint scopes the cache cell to the configured credential, so
|
||||
// swapping keys never reuses the previous account's quota via TTL or stale fallback.
|
||||
const identity = createHash("sha256").update(first.key).digest("hex")
|
||||
return cached(
|
||||
id,
|
||||
async () => {
|
||||
const responses = await Promise.allSettled(
|
||||
group.map((candidate) => query(candidate.providerID, candidate.key, fetcher)),
|
||||
)
|
||||
const index = responses.findIndex((response) => response.status === "fulfilled")
|
||||
if (index === -1) {
|
||||
return unavailable(
|
||||
id,
|
||||
first.providerID,
|
||||
shared ? "Direct MiniMax" : first.label,
|
||||
bindings[first.providerID].manage,
|
||||
)
|
||||
}
|
||||
|
||||
const candidate = group[index]
|
||||
const response = responses[index]
|
||||
if (response.status !== "fulfilled") {
|
||||
return unavailable(id, candidate.providerID, "Direct MiniMax", bindings[candidate.providerID].manage)
|
||||
}
|
||||
return normalize(response.value, {
|
||||
id,
|
||||
providerID: candidate.providerID,
|
||||
sourceLabel: candidate.label,
|
||||
managementUrl: bindings[candidate.providerID].manage,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
})
|
||||
},
|
||||
identity,
|
||||
)
|
||||
}),
|
||||
candidates
|
||||
.filter((candidate) => candidate.key.startsWith("sk-cp"))
|
||||
.map((candidate) => {
|
||||
const binding = bindings[candidate.providerID]
|
||||
const id = `minimax-direct-${binding.region}`
|
||||
// The key fingerprint scopes the cache cell to the configured credential, so
|
||||
// swapping keys never reuses the previous account's quota via TTL or stale fallback.
|
||||
const identity = createHash("sha256").update(candidate.key).digest("hex")
|
||||
return cached(
|
||||
id,
|
||||
() =>
|
||||
query(candidate.providerID, candidate.key, fetcher)
|
||||
.then((native) =>
|
||||
normalize(native, {
|
||||
id,
|
||||
providerID: candidate.providerID,
|
||||
sourceLabel: candidate.label,
|
||||
managementUrl: binding.manage,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
}),
|
||||
)
|
||||
.catch(() => unavailable(id, candidate.providerID, candidate.label, binding.manage)),
|
||||
identity,
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ describe("managed provider usage", () => {
|
||||
windows: [
|
||||
{
|
||||
id: "byteplus-plan:monthly",
|
||||
label: "Monthly quota",
|
||||
resource: "subscription",
|
||||
period: { unit: "month", value: 1 },
|
||||
remaining: 75,
|
||||
used: 25,
|
||||
limit: 100,
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("MiniMax usage normalization", () => {
|
||||
remaining: 80,
|
||||
used: 20,
|
||||
limit: 100,
|
||||
period: { unit: "hour", value: 5 },
|
||||
resetAt: "2026-06-19T05:00:00.000Z",
|
||||
})
|
||||
expect(direct.windows[0]?.remaining).not.toBe(1)
|
||||
@@ -112,6 +113,7 @@ describe("MiniMax usage normalization", () => {
|
||||
orientation: "amount",
|
||||
remaining: 150,
|
||||
limit: 150,
|
||||
period: { unit: "week", value: 1 },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -151,7 +153,7 @@ describe("MiniMax usage transport and detection", () => {
|
||||
expect(fn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("deduplicates a shared credential while probing fixed regions", async () => {
|
||||
test("keeps same-key providers as independent regional plans", async () => {
|
||||
const fn = mock((url: string | URL | Request) =>
|
||||
Promise.resolve(
|
||||
String(url).includes("api.minimax.io")
|
||||
@@ -168,8 +170,17 @@ describe("MiniMax usage transport and detection", () => {
|
||||
)
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(2)
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0]).toMatchObject({ id: "minimax-direct-shared", providerID: "minimax-cn-coding-plan" })
|
||||
expect(items).toHaveLength(2)
|
||||
expect(items.find((item) => item.id === "minimax-direct-global")).toMatchObject({
|
||||
providerID: "minimax-coding-plan",
|
||||
sourceLabel: "MiniMax Global",
|
||||
fetchState: "unavailable",
|
||||
})
|
||||
expect(items.find((item) => item.id === "minimax-direct-china")).toMatchObject({
|
||||
providerID: "minimax-cn-coding-plan",
|
||||
sourceLabel: "MiniMax China",
|
||||
fetchState: "ready",
|
||||
})
|
||||
expect(JSON.stringify(items)).not.toContain("sk-cp-shared")
|
||||
})
|
||||
|
||||
@@ -190,15 +201,15 @@ describe("MiniMax usage transport and detection", () => {
|
||||
expect(JSON.stringify(seen)).not.toContain("sk-cp")
|
||||
})
|
||||
|
||||
test("returns one unavailable item when an ambiguous key fails everywhere", async () => {
|
||||
test("returns per-provider unavailable items when every query fails", async () => {
|
||||
const fn = mock(() => Promise.resolve(response({ message: "raw failure" }, 500)))
|
||||
const items = await direct(
|
||||
[candidate("minimax-coding-plan", "sk-cp-shared"), candidate("minimax-cn-coding-plan", "sk-cp-shared")],
|
||||
fn as unknown as typeof fetch,
|
||||
)
|
||||
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0]).toMatchObject({ id: "minimax-direct-shared", fetchState: "unavailable" })
|
||||
expect(items).toHaveLength(2)
|
||||
expect(items.every((item) => item.fetchState === "unavailable")).toBe(true)
|
||||
expect(JSON.stringify(items)).not.toContain("raw failure")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, mock } from "bun:test"
|
||||
import { describe, expect, mock, setSystemTime } from "bun:test"
|
||||
import { Context, Deferred, Effect, Exit, Fiber, Layer, Scope } from "effect"
|
||||
import { Catalog } from "../src/catalog"
|
||||
import { Credential } from "../src/credential"
|
||||
@@ -406,6 +406,54 @@ describe("ProviderUsage location service", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("expires the success cache and retries failures on the shorter error TTL", () =>
|
||||
Effect.gen(function* () {
|
||||
const calls = { direct: 0, cloud: 0 }
|
||||
const responses = [native(80), new Response("upstream failure", { status: 503 }), native(70)]
|
||||
const base = Date.parse("2026-08-12T00:00:00.000Z")
|
||||
setSystemTime(new Date(base))
|
||||
const scope = yield* Scope.make()
|
||||
const usage = Context.get(
|
||||
yield* Layer.buildWithScope(
|
||||
configuredLayer({
|
||||
calls,
|
||||
direct: "sk-cp-direct",
|
||||
accountID: "org",
|
||||
transport: {
|
||||
fetch: mock(() => {
|
||||
calls.direct++
|
||||
return Promise.resolve(responses.shift()!)
|
||||
}) as unknown as typeof fetch,
|
||||
plans: async () => [],
|
||||
byok: async () => [],
|
||||
usage: async () => {
|
||||
throw new Error("unused")
|
||||
},
|
||||
},
|
||||
}),
|
||||
scope,
|
||||
),
|
||||
ProviderUsage.Service,
|
||||
)
|
||||
|
||||
expect((yield* usage.get()).items[0]).toMatchObject({ fetchState: "ready", windows: [{ remaining: 80 }] })
|
||||
expect((yield* usage.get()).items[0]).toMatchObject({ fetchState: "ready" })
|
||||
expect(calls.direct).toBe(1)
|
||||
|
||||
// Success TTL (60s) elapsed: the next get refetches and degrades to stale on failure.
|
||||
setSystemTime(new Date(base + 61_000))
|
||||
expect((yield* usage.get()).items[0]).toMatchObject({ fetchState: "stale", windows: [{ remaining: 80 }] })
|
||||
expect((yield* usage.get()).items[0]).toMatchObject({ fetchState: "stale" })
|
||||
expect(calls.direct).toBe(2)
|
||||
|
||||
// Error TTL (10s) elapsed: the failure is retried and recovers.
|
||||
setSystemTime(new Date(base + 72_000))
|
||||
expect((yield* usage.get()).items[0]).toMatchObject({ fetchState: "ready", windows: [{ remaining: 70 }] })
|
||||
expect(calls.direct).toBe(3)
|
||||
yield* Scope.close(scope, Exit.void)
|
||||
}).pipe(Effect.ensuring(Effect.sync(() => setSystemTime()))),
|
||||
)
|
||||
|
||||
it.live("prunes authoritative removal but preserves a transient credential failure", () =>
|
||||
Effect.gen(function* () {
|
||||
const removedCalls = { direct: 0, cloud: 0 }
|
||||
@@ -470,7 +518,7 @@ describe("ProviderUsage location service", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("does not duplicate shared usage when one provider credential fails", () =>
|
||||
it.live("keeps same-key providers independent when one credential fails", () =>
|
||||
Effect.gen(function* () {
|
||||
const calls = { direct: 0, cloud: 0 }
|
||||
let failure: "global" | undefined
|
||||
@@ -489,17 +537,19 @@ describe("ProviderUsage location service", () => {
|
||||
ProviderUsage.Service,
|
||||
)
|
||||
|
||||
expect((yield* usage.get()).items).toHaveLength(1)
|
||||
expect((yield* usage.get()).items).toHaveLength(2)
|
||||
failure = "global"
|
||||
const result = yield* usage.get()
|
||||
|
||||
expect(result.items).toHaveLength(1)
|
||||
expect(result.items[0]).toMatchObject({ providerID: "minimax-cn-coding-plan", fetchState: "ready" })
|
||||
expect(calls.direct).toBe(3)
|
||||
expect(result.items).toHaveLength(2)
|
||||
expect(result.items.find((item) => item.id === "minimax-direct-global")).toMatchObject({ fetchState: "stale" })
|
||||
expect(result.items.find((item) => item.id === "minimax-direct-china")).toMatchObject({ fetchState: "ready" })
|
||||
// The surviving sibling serves from cache; the failed one is not refetched.
|
||||
expect(calls.direct).toBe(2)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("preserves failed shared usage when the surviving provider rotates credentials", () =>
|
||||
it.live("refreshes a rotated credential while preserving the failed sibling's cached usage", () =>
|
||||
Effect.gen(function* () {
|
||||
const calls = { direct: 0, cloud: 0 }
|
||||
let failure: "global" | undefined
|
||||
@@ -519,23 +569,21 @@ describe("ProviderUsage location service", () => {
|
||||
ProviderUsage.Service,
|
||||
)
|
||||
|
||||
expect((yield* usage.get()).items).toHaveLength(1)
|
||||
expect((yield* usage.get()).items).toHaveLength(2)
|
||||
failure = "global"
|
||||
key = "sk-cp-rotated"
|
||||
const result = yield* usage.get()
|
||||
|
||||
expect(result.items).toHaveLength(2)
|
||||
expect(result.items.find((item) => item.id === "minimax-direct-shared")).toMatchObject({ fetchState: "stale" })
|
||||
expect(result.items.find((item) => item.id === "minimax-direct-global")).toMatchObject({ fetchState: "stale" })
|
||||
expect(result.items.find((item) => item.id === "minimax-direct-china")).toMatchObject({ fetchState: "ready" })
|
||||
expect(calls.direct).toBe(3)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("preserves shared usage when the surviving same-key query fails", () =>
|
||||
it.live("omits a failed provider with no cached usage", () =>
|
||||
Effect.gen(function* () {
|
||||
const calls = { direct: 0, cloud: 0 }
|
||||
let failure: "global" | undefined
|
||||
let queryFailure = false
|
||||
const scope = yield* Scope.make()
|
||||
const usage = Context.get(
|
||||
yield* Layer.buildWithScope(
|
||||
@@ -544,32 +592,18 @@ describe("ProviderUsage location service", () => {
|
||||
accountID: "org",
|
||||
config: { china: true },
|
||||
direct: "sk-cp-shared",
|
||||
failure: () => failure,
|
||||
transport: {
|
||||
fetch: mock(() => {
|
||||
calls.direct++
|
||||
return queryFailure ? Promise.reject(new Error("private query failure")) : Promise.resolve(native(80))
|
||||
}) as unknown as typeof fetch,
|
||||
plans: async () => [],
|
||||
byok: async () => [],
|
||||
usage: async () => {
|
||||
throw new Error("unused")
|
||||
},
|
||||
},
|
||||
failure: () => "global" as const,
|
||||
}),
|
||||
scope,
|
||||
),
|
||||
ProviderUsage.Service,
|
||||
)
|
||||
|
||||
expect((yield* usage.get()).items).toHaveLength(1)
|
||||
failure = "global"
|
||||
queryFailure = true
|
||||
const result = yield* usage.get()
|
||||
|
||||
expect(result.items).toHaveLength(1)
|
||||
expect(result.items[0]).toMatchObject({ id: "minimax-direct-shared", fetchState: "stale" })
|
||||
expect(JSON.stringify(result)).not.toContain("private query failure")
|
||||
expect(result.items[0]).toMatchObject({ id: "minimax-direct-china", fetchState: "ready" })
|
||||
expect(calls.direct).toBe(1)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -6,10 +6,17 @@
|
||||
* so the two surfaces can never drift; labels are injectable for i18n.
|
||||
*/
|
||||
|
||||
export interface UsagePeriodLike {
|
||||
unit: "hour" | "day" | "week" | "month"
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface UsageWindowLike {
|
||||
state: "active" | "exhausted" | "unlimited" | "not_in_plan" | "unknown"
|
||||
orientation: "used_percent" | "remaining_percent" | "amount" | "count"
|
||||
unit: string
|
||||
resource: string
|
||||
period?: UsagePeriodLike
|
||||
used?: number
|
||||
remaining?: number
|
||||
limit?: number
|
||||
@@ -24,6 +31,17 @@ export interface UsageLabels {
|
||||
remaining(value: string): string
|
||||
remainingOf(value: string, limit: string): string
|
||||
usedOf(value: string, limit: string): string
|
||||
quota: string
|
||||
daily: string
|
||||
weekly: string
|
||||
monthly: string
|
||||
hours(count: number): string
|
||||
days(count: number): string
|
||||
weeks(count: number): string
|
||||
months(count: number): string
|
||||
/** Display name for MiniMax's pooled "general" resource. */
|
||||
shared: string
|
||||
scoped(resource: string, period: string): string
|
||||
}
|
||||
|
||||
export const english: UsageLabels = {
|
||||
@@ -35,6 +53,30 @@ export const english: UsageLabels = {
|
||||
remaining: (value) => `${value} remaining`,
|
||||
remainingOf: (value, limit) => `${value} of ${limit} remaining`,
|
||||
usedOf: (value, limit) => `${value} of ${limit} used`,
|
||||
quota: "Quota",
|
||||
daily: "Daily quota",
|
||||
weekly: "Weekly quota",
|
||||
monthly: "Monthly quota",
|
||||
hours: (count) => `${count}-hour quota`,
|
||||
days: (count) => `${count}-day quota`,
|
||||
weeks: (count) => `${count}-week quota`,
|
||||
months: (count) => `${count}-month quota`,
|
||||
shared: "Shared",
|
||||
scoped: (resource, period) => `${resource} · ${period}`,
|
||||
}
|
||||
|
||||
const period = (value: UsagePeriodLike, labels: UsageLabels) => {
|
||||
if (value.unit === "hour") return labels.hours(value.value)
|
||||
if (value.unit === "day") return value.value === 1 ? labels.daily : labels.days(value.value)
|
||||
if (value.unit === "week") return value.value === 1 ? labels.weekly : labels.weeks(value.value)
|
||||
return value.value === 1 ? labels.monthly : labels.months(value.value)
|
||||
}
|
||||
|
||||
export const windowLabel = (window: UsageWindowLike, labels: UsageLabels = english) => {
|
||||
const phrase = window.period ? period(window.period, labels) : labels.quota
|
||||
// Plan-level windows ("subscription") are the whole card; named resources prefix theirs.
|
||||
if (window.resource === "subscription") return phrase
|
||||
return labels.scoped(window.resource === "general" ? labels.shared : window.resource, phrase)
|
||||
}
|
||||
|
||||
const number = (value: number) => value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
||||
|
||||
@@ -3,6 +3,7 @@ import { existsSync } from "fs"
|
||||
import * as vscode from "vscode"
|
||||
import type {
|
||||
KiloClient,
|
||||
ProviderUsage,
|
||||
Session,
|
||||
SessionStatus,
|
||||
Event,
|
||||
@@ -367,8 +368,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
/** Cached notificationsLoaded payload */
|
||||
private cachedNotificationsMessage: NotificationsMessage | null = null
|
||||
/** Cached provider usage payload for profile view remounts and temporary disconnects. */
|
||||
private cachedProviderUsageMessage: { type: "providerUsageLoaded"; data: unknown } | null = null
|
||||
private providerUsageRequested = false
|
||||
private cachedProviderUsageMessage: { type: "providerUsageLoaded"; data: ProviderUsage } | null = null
|
||||
private providerUsageGeneration = 0
|
||||
private pendingKiloModel: { modelID?: string; agent?: string } | null = null
|
||||
private pendingReviewComments: { comments: unknown[]; autoSend: boolean }[] = []
|
||||
@@ -575,7 +575,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.postMessage({ type: "workspaceDirectoryChanged", directory: directory ?? "" })
|
||||
this.postMessage({ type: "configBindingExpired", reason: "project-changed" })
|
||||
this.requirements.clear()
|
||||
if (this.providerUsageRequested) void this.fetchAndSendProviderUsage()
|
||||
}
|
||||
|
||||
public setDiffVirtualProvider(provider: import("./DiffVirtualProvider").DiffVirtualProvider): void {
|
||||
@@ -1560,18 +1559,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
await handleRefreshProfile(this.authCtx)
|
||||
return true
|
||||
}
|
||||
if (message.type === "requestProviderUsage") {
|
||||
await this.fetchAndSendProviderUsage()
|
||||
return true
|
||||
}
|
||||
if (message.type === "refreshProviderUsage") {
|
||||
await this.fetchAndSendProviderUsage(true)
|
||||
return true
|
||||
}
|
||||
if (message.type === "releaseProviderUsage") {
|
||||
// Profile view unmounted: stop background refreshes on directory/auth
|
||||
// changes until the view requests usage again.
|
||||
this.providerUsageRequested = false
|
||||
await this.fetchAndSendProviderUsage()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -2370,32 +2359,28 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchAndSendProviderUsage(force = false): Promise<void> {
|
||||
this.providerUsageRequested = true
|
||||
private async fetchAndSendProviderUsage(): Promise<void> {
|
||||
const generation = ++this.providerUsageGeneration
|
||||
const client = this.client
|
||||
if (!client) {
|
||||
if (this.cachedProviderUsageMessage) this.postMessage(this.cachedProviderUsageMessage)
|
||||
this.postMessage(
|
||||
this.cachedProviderUsageMessage ?? {
|
||||
type: "providerUsageLoaded",
|
||||
error: "Provider usage could not be loaded.",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const directory = this.getProjectDirectory(this.currentSession?.id)
|
||||
const result = await (
|
||||
force ? client.kilocode.providerUsage.refresh({ directory }) : client.kilocode.providerUsage.get({ directory })
|
||||
).catch((error) => {
|
||||
const result = await client.kilocode.providerUsage.refresh({ directory }).catch((error) => {
|
||||
console.error("[Kilo New] KiloProvider: Failed to fetch provider usage:", error)
|
||||
return undefined
|
||||
})
|
||||
if (generation !== this.providerUsageGeneration) return
|
||||
if (!result?.data) {
|
||||
if (this.cachedProviderUsageMessage) {
|
||||
// Re-serve the cached data, but tell the user when their explicit
|
||||
// refresh failed instead of silently presenting stale data as fresh.
|
||||
this.postMessage(
|
||||
force
|
||||
? { ...this.cachedProviderUsageMessage, error: "Provider usage could not be refreshed." }
|
||||
: this.cachedProviderUsageMessage,
|
||||
)
|
||||
this.postMessage({ ...this.cachedProviderUsageMessage, error: "Provider usage could not be refreshed." })
|
||||
return
|
||||
}
|
||||
this.postMessage({ type: "providerUsageLoaded", error: "Provider usage could not be loaded." })
|
||||
@@ -4038,12 +4023,19 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
postMessage: (msg) => this.postMessage(msg),
|
||||
getWorkspaceDirectory: () => this.getWorkspaceDirectory(),
|
||||
disposeGlobal: () => this.disposeGlobal(),
|
||||
invalidateProviderUsage: () => this.invalidateProviderUsage(),
|
||||
fetchAndSendProviders: () => this.fetchAndSendProviders(),
|
||||
fetchAndSendAgents: () => this.fetchAndSendAgents(),
|
||||
fetchAndSendSpeechToTextModels: () => this.fetchAndSendSpeechToTextModels(),
|
||||
}
|
||||
}
|
||||
|
||||
private invalidateProviderUsage(): void {
|
||||
this.providerUsageGeneration++
|
||||
this.cachedProviderUsageMessage = null
|
||||
this.postMessage({ type: "providerUsageLoaded", reset: true })
|
||||
}
|
||||
|
||||
private async disposeGlobal(): Promise<void> {
|
||||
if (!this.client) return
|
||||
|
||||
@@ -4188,9 +4180,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
/** Re-fetch all server-side state after an auth change. */
|
||||
private async reloadAfterAuthChange(): Promise<void> {
|
||||
this.requirements.clear()
|
||||
this.providerUsageGeneration++
|
||||
this.cachedProviderUsageMessage = null
|
||||
if (this.providerUsageRequested) this.postMessage({ type: "providerUsageLoaded", reset: true })
|
||||
this.invalidateProviderUsage()
|
||||
await this.fetchAndSendConfig()
|
||||
await Promise.all([
|
||||
this.fetchAndSendProviders(),
|
||||
@@ -4199,7 +4189,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.fetchAndSendCommands(),
|
||||
this.fetchAndSendIndexingStatus(),
|
||||
this.fetchAndSendNotifications(),
|
||||
this.providerUsageRequested ? this.fetchAndSendProviderUsage() : Promise.resolve(),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface AuthContext {
|
||||
postMessage(msg: unknown): void
|
||||
getWorkspaceDirectory(): string
|
||||
disposeGlobal(): Promise<void>
|
||||
invalidateProviderUsage(): void
|
||||
fetchAndSendProviders(): Promise<void>
|
||||
fetchAndSendAgents(): Promise<void>
|
||||
fetchAndSendSpeechToTextModels(): Promise<void>
|
||||
@@ -60,6 +61,7 @@ export async function handleLogin(ctx: AuthContext, attempt: number, getAttempt:
|
||||
|
||||
console.log("[Kilo New] KiloProvider: 🔐 Login successful")
|
||||
|
||||
ctx.invalidateProviderUsage()
|
||||
await ctx.disposeGlobal()
|
||||
|
||||
// Step 3: Fetch profile and push to webview
|
||||
@@ -85,6 +87,7 @@ export async function handleLogout(ctx: AuthContext): Promise<void> {
|
||||
console.log("[Kilo New] KiloProvider: 🚪 Logged out successfully")
|
||||
ctx.postMessage({ type: "profileData", data: null })
|
||||
|
||||
ctx.invalidateProviderUsage()
|
||||
await ctx.disposeGlobal()
|
||||
|
||||
await ctx.fetchAndSendProviders()
|
||||
@@ -119,6 +122,7 @@ export async function handleSetOrganization(ctx: AuthContext, organizationId: st
|
||||
return
|
||||
}
|
||||
|
||||
ctx.invalidateProviderUsage()
|
||||
await ctx.disposeGlobal()
|
||||
|
||||
// Org switch succeeded — refresh profile and providers independently (best-effort)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { ProviderUsage, ProviderUsageWindow } from "@kilocode/sdk/v2/client"
|
||||
import { formatWindow, windowProgress } from "@kilocode/kilo-gateway/provider-usage"
|
||||
import { formatWindow, windowLabel, windowProgress } from "@kilocode/kilo-gateway/provider-usage"
|
||||
|
||||
const { KiloProvider } = await import("../../src/KiloProvider")
|
||||
|
||||
@@ -10,43 +10,46 @@ const data: ProviderUsage = {
|
||||
}
|
||||
|
||||
type Internals = {
|
||||
providerUsageRequested: boolean
|
||||
cachedProviderUsageMessage: unknown
|
||||
fetchAndSendProviderUsage: (force?: boolean) => Promise<void>
|
||||
handleProfileDataMessage: (message: { type: string }) => Promise<boolean>
|
||||
fetchAndSendProviderUsage: () => Promise<void>
|
||||
reloadAfterAuthChange: () => Promise<void>
|
||||
postMessage: (message: unknown) => void
|
||||
fetchAndSendConfig: () => Promise<void>
|
||||
fetchAndSendProviders: () => Promise<void>
|
||||
fetchAndSendAgents: () => Promise<void>
|
||||
fetchAndSendSkills: () => Promise<void>
|
||||
fetchAndSendCommands: () => Promise<void>
|
||||
fetchAndSendIndexingStatus: () => Promise<void>
|
||||
fetchAndSendNotifications: () => Promise<void>
|
||||
}
|
||||
|
||||
type UsageClient = {
|
||||
get: (input: { directory?: string }) => Promise<unknown>
|
||||
refresh?: (input: { directory?: string }) => Promise<unknown>
|
||||
refresh: (input: { directory?: string }) => Promise<unknown>
|
||||
}
|
||||
|
||||
// Answers any SDK endpoint outside the fake usage client with a benign empty
|
||||
// response, so tests never have to mirror KiloProvider's internal fetcher list.
|
||||
const benign = (value: unknown): unknown =>
|
||||
typeof value === "function"
|
||||
? value
|
||||
: new Proxy(() => {}, {
|
||||
get: (_, prop) =>
|
||||
prop === "then" ? undefined : benign((value as Record<PropertyKey, unknown> | undefined)?.[prop]),
|
||||
apply: () => Promise.resolve({ data: [] }),
|
||||
})
|
||||
|
||||
function bridge(usage: UsageClient) {
|
||||
const messages: unknown[] = []
|
||||
const provider = new KiloProvider(
|
||||
{} as never,
|
||||
{ getClient: () => ({ kilocode: { providerUsage: usage } }) } as never,
|
||||
{ getClient: () => benign({ kilocode: { providerUsage: usage } }) } as never,
|
||||
undefined,
|
||||
{ projectDirectory: "/repo" },
|
||||
)
|
||||
const internal = provider as unknown as Internals
|
||||
internal.postMessage = (message) => messages.push(message)
|
||||
return { internal, messages }
|
||||
return { provider, internal, messages }
|
||||
}
|
||||
|
||||
const usageMessages = (messages: unknown[]) =>
|
||||
messages.filter((message) => (message as { type?: string }).type === "providerUsageLoaded")
|
||||
|
||||
describe("provider usage presentation", () => {
|
||||
const window = (value: Partial<ProviderUsageWindow>): ProviderUsageWindow => ({
|
||||
id: "quota",
|
||||
label: "Quota",
|
||||
resource: "general",
|
||||
unit: "percent",
|
||||
orientation: "remaining_percent",
|
||||
@@ -66,17 +69,20 @@ describe("provider usage presentation", () => {
|
||||
expect(formatWindow(window({ state: "unlimited" }))).toBe("Unlimited")
|
||||
expect(formatWindow(window({ state: "not_in_plan" }))).toBe("Not in plan")
|
||||
})
|
||||
|
||||
it("composes window labels from structured periods instead of wire strings", () => {
|
||||
expect(windowLabel(window({ resource: "subscription", period: { unit: "month", value: 1 } }))).toBe("Monthly quota")
|
||||
expect(windowLabel(window({ resource: "subscription", period: { unit: "day", value: 3 } }))).toBe("3-day quota")
|
||||
expect(windowLabel(window({ period: { unit: "hour", value: 5 } }))).toBe("Shared · 5-hour quota")
|
||||
expect(windowLabel(window({ period: { unit: "week", value: 1 } }))).toBe("Shared · Weekly quota")
|
||||
expect(windowLabel(window({ resource: "image" }))).toBe("image · Quota")
|
||||
})
|
||||
})
|
||||
|
||||
describe("KiloProvider provider usage bridge", () => {
|
||||
it("uses cache-aware GET and explicit refresh POST", async () => {
|
||||
const get: Array<{ directory?: string }> = []
|
||||
it("loads usage only through an explicit refresh", async () => {
|
||||
const refresh: Array<{ directory?: string }> = []
|
||||
const { internal, messages } = bridge({
|
||||
get: async (input) => {
|
||||
get.push(input)
|
||||
return { data }
|
||||
},
|
||||
refresh: async (input) => {
|
||||
refresh.push(input)
|
||||
return { data }
|
||||
@@ -84,36 +90,26 @@ describe("KiloProvider provider usage bridge", () => {
|
||||
})
|
||||
|
||||
await internal.fetchAndSendProviderUsage()
|
||||
await internal.fetchAndSendProviderUsage(true)
|
||||
|
||||
expect(get).toEqual([{ directory: "/repo" }])
|
||||
expect(refresh).toEqual([{ directory: "/repo" }])
|
||||
expect(messages).toEqual([
|
||||
{ type: "providerUsageLoaded", data },
|
||||
{ type: "providerUsageLoaded", data },
|
||||
])
|
||||
expect(internal.providerUsageRequested).toBe(true)
|
||||
expect(messages).toEqual([{ type: "providerUsageLoaded", data }])
|
||||
expect(internal.cachedProviderUsageMessage).toEqual({ type: "providerUsageLoaded", data })
|
||||
})
|
||||
|
||||
it("surfaces a failed forced refresh alongside the cached data", async () => {
|
||||
const { internal, messages } = bridge({
|
||||
get: async () => ({ data }),
|
||||
refresh: async () => ({ error: { _tag: "ServiceUnavailable" } }),
|
||||
})
|
||||
|
||||
internal.cachedProviderUsageMessage = { type: "providerUsageLoaded", data }
|
||||
await internal.fetchAndSendProviderUsage()
|
||||
await internal.fetchAndSendProviderUsage(true)
|
||||
|
||||
expect(messages).toEqual([
|
||||
{ type: "providerUsageLoaded", data },
|
||||
{ type: "providerUsageLoaded", data, error: "Provider usage could not be refreshed." },
|
||||
])
|
||||
expect(messages).toEqual([{ type: "providerUsageLoaded", data, error: "Provider usage could not be refreshed." }])
|
||||
})
|
||||
|
||||
it("posts a terminal loading error when the backend has no cached response", async () => {
|
||||
const { internal, messages } = bridge({
|
||||
get: async () => ({ error: { _tag: "ServiceUnavailable" } }),
|
||||
refresh: async () => ({ error: { _tag: "ServiceUnavailable" } }),
|
||||
})
|
||||
|
||||
await internal.fetchAndSendProviderUsage()
|
||||
@@ -121,35 +117,76 @@ describe("KiloProvider provider usage bridge", () => {
|
||||
expect(messages).toEqual([{ type: "providerUsageLoaded", error: "Provider usage could not be loaded." }])
|
||||
})
|
||||
|
||||
it("refreshes usage after auth invalidation only after the profile requested it", async () => {
|
||||
const provider = new KiloProvider({} as never, {} as never)
|
||||
const internal = provider as unknown as Internals
|
||||
let usage = 0
|
||||
internal.fetchAndSendConfig = async () => {}
|
||||
internal.fetchAndSendProviders = async () => {}
|
||||
internal.fetchAndSendAgents = async () => {}
|
||||
internal.fetchAndSendSkills = async () => {}
|
||||
internal.fetchAndSendCommands = async () => {}
|
||||
internal.fetchAndSendIndexingStatus = async () => {}
|
||||
internal.fetchAndSendNotifications = async () => {}
|
||||
internal.fetchAndSendProviderUsage = async () => {
|
||||
usage++
|
||||
}
|
||||
|
||||
await internal.reloadAfterAuthChange()
|
||||
expect(usage).toBe(0)
|
||||
internal.providerUsageRequested = true
|
||||
await internal.reloadAfterAuthChange()
|
||||
expect(usage).toBe(1)
|
||||
})
|
||||
|
||||
it("releases the background refresh latch when the profile view unmounts", async () => {
|
||||
const { internal } = bridge({ get: async () => ({ data }) })
|
||||
it("invalidates cached usage without reloading on auth change", async () => {
|
||||
const requests: unknown[] = []
|
||||
const { internal, messages } = bridge({
|
||||
refresh: async (input) => {
|
||||
requests.push(input)
|
||||
return { data: { generatedAt: "a", items: [] } }
|
||||
},
|
||||
})
|
||||
|
||||
await internal.fetchAndSendProviderUsage()
|
||||
expect(internal.providerUsageRequested).toBe(true)
|
||||
await internal.reloadAfterAuthChange()
|
||||
|
||||
expect(await internal.handleProfileDataMessage({ type: "releaseProviderUsage" })).toBe(true)
|
||||
expect(internal.providerUsageRequested).toBe(false)
|
||||
expect(usageMessages(messages)).toEqual([
|
||||
{ type: "providerUsageLoaded", data: { generatedAt: "a", items: [] } },
|
||||
{ type: "providerUsageLoaded", reset: true },
|
||||
])
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(internal.cachedProviderUsageMessage).toBeNull()
|
||||
})
|
||||
|
||||
it("resets usage without fetching when auth changes before the profile is opened", async () => {
|
||||
const requests: unknown[] = []
|
||||
const { internal, messages } = bridge({
|
||||
refresh: async (input) => {
|
||||
requests.push(input)
|
||||
return { data }
|
||||
},
|
||||
})
|
||||
|
||||
await internal.reloadAfterAuthChange()
|
||||
|
||||
expect(requests).toEqual([])
|
||||
expect(usageMessages(messages)).toEqual([{ type: "providerUsageLoaded", reset: true }])
|
||||
})
|
||||
|
||||
it("drops an in-flight usage response from the previous account", async () => {
|
||||
let release!: (value: { data: ProviderUsage }) => void
|
||||
const first = new Promise<{ data: ProviderUsage }>((resolve) => (release = resolve))
|
||||
const calls: unknown[] = []
|
||||
const { internal, messages } = bridge({
|
||||
refresh: (input) => {
|
||||
calls.push(input)
|
||||
return first
|
||||
},
|
||||
})
|
||||
|
||||
const hung = internal.fetchAndSendProviderUsage()
|
||||
await internal.reloadAfterAuthChange()
|
||||
release({ data: { generatedAt: "a", items: [] } })
|
||||
await hung
|
||||
|
||||
expect(usageMessages(messages)).toEqual([{ type: "providerUsageLoaded", reset: true }])
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(internal.cachedProviderUsageMessage).toBeNull()
|
||||
})
|
||||
|
||||
it("invalidates cached usage without fetching when the workspace directory changes", async () => {
|
||||
const requests: unknown[] = []
|
||||
const { provider, internal, messages } = bridge({
|
||||
refresh: async (input) => {
|
||||
requests.push(input)
|
||||
return { data }
|
||||
},
|
||||
})
|
||||
|
||||
await internal.fetchAndSendProviderUsage()
|
||||
provider.setProjectDirectory("/other")
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(internal.cachedProviderUsageMessage).toBeNull()
|
||||
expect(messages).toContainEqual({ type: "workspaceDirectoryChanged", directory: "/other" })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -364,9 +364,7 @@ const AppContent: Component = () => {
|
||||
providerUsageError={server.providerUsageError()}
|
||||
deviceAuth={server.deviceAuth()}
|
||||
onLogin={server.startLogin}
|
||||
onRequestProviderUsage={server.requestProviderUsage}
|
||||
onRefreshProviderUsage={server.refreshProviderUsage}
|
||||
onReleaseProviderUsage={server.releaseProviderUsage}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={currentView() === "settings"}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Show, createSignal, createMemo, createEffect, onCleanup, onMount } from "solid-js"
|
||||
import { Component, Show, createSignal, createMemo, createEffect, onMount } from "solid-js"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
@@ -19,9 +19,7 @@ export interface ProfileViewProps {
|
||||
providerUsageLoading?: boolean
|
||||
providerUsageError?: string
|
||||
onLogin: () => void
|
||||
onRequestProviderUsage?: () => void
|
||||
onRefreshProviderUsage?: () => void
|
||||
onReleaseProviderUsage?: () => void
|
||||
}
|
||||
|
||||
const formatBalance = (amount: number): string => {
|
||||
@@ -43,15 +41,12 @@ const ProfileView: Component<ProfileViewProps> = (props) => {
|
||||
|
||||
const personal = createMemo(() => props.profileData?.profile.hasPersonalAccount !== false)
|
||||
|
||||
// Always fetch fresh profile+balance when navigating to this view
|
||||
// Load current profile and usage when navigating to this view.
|
||||
onMount(() => {
|
||||
vscode.postMessage({ type: "refreshProfile" })
|
||||
props.onRequestProviderUsage?.()
|
||||
props.onRefreshProviderUsage?.()
|
||||
})
|
||||
|
||||
// Stop background usage refreshes while this view is not visible
|
||||
onCleanup(() => props.onReleaseProviderUsage?.())
|
||||
|
||||
// Reset pending target whenever profileData changes (success or failure both send a fresh profile)
|
||||
createEffect(() => {
|
||||
props.profileData // track
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Spinner } from "@kilocode/kilo-ui/spinner"
|
||||
import { Tag } from "@kilocode/kilo-ui/tag"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { localeToBcp47 } from "../../context/language-utils"
|
||||
import { formatWindow, windowProgress } from "@kilocode/kilo-gateway/provider-usage"
|
||||
import { formatWindow, windowLabel, windowProgress } from "@kilocode/kilo-gateway/provider-usage"
|
||||
|
||||
export interface ProviderUsageCardsProps {
|
||||
data: ProviderUsageData | undefined
|
||||
@@ -38,6 +38,16 @@ const labels = (language: Language) => ({
|
||||
remaining: (value: string) => language.t("profile.usage.window.remaining", { value }),
|
||||
remainingOf: (value: string, limit: string) => language.t("profile.usage.window.remainingOf", { value, limit }),
|
||||
usedOf: (value: string, limit: string) => language.t("profile.usage.window.usedOf", { value, limit }),
|
||||
quota: language.t("profile.usage.window.quota"),
|
||||
daily: language.t("profile.usage.window.daily"),
|
||||
weekly: language.t("profile.usage.window.weekly"),
|
||||
monthly: language.t("profile.usage.window.monthly"),
|
||||
hours: (count: number) => language.t("profile.usage.window.hours", { count: String(count) }),
|
||||
days: (count: number) => language.t("profile.usage.window.days", { count: String(count) }),
|
||||
weeks: (count: number) => language.t("profile.usage.window.weeks", { count: String(count) }),
|
||||
months: (count: number) => language.t("profile.usage.window.months", { count: String(count) }),
|
||||
shared: language.t("profile.usage.window.shared"),
|
||||
scoped: (resource: string, period: string) => language.t("profile.usage.window.scoped", { resource, period }),
|
||||
})
|
||||
|
||||
const variant = (item: ProviderUsageSnapshot) => {
|
||||
@@ -104,6 +114,7 @@ const UsageCard: Component<{
|
||||
{(window) => {
|
||||
const progress = () => windowProgress(window)
|
||||
const value = () => formatWindow(window, labels(props.language))
|
||||
const title = () => windowLabel(window, labels(props.language))
|
||||
return (
|
||||
<div class="provider-usage-row">
|
||||
<Show when={progress() !== undefined}>
|
||||
@@ -113,22 +124,24 @@ const UsageCard: Component<{
|
||||
maxValue={100}
|
||||
showValueLabel
|
||||
getValueLabel={value}
|
||||
aria-label={`${window.label}: ${value()}`}
|
||||
aria-label={`${title()}: ${value()}`}
|
||||
aria-valuetext={value()}
|
||||
>
|
||||
{window.label}
|
||||
{title()}
|
||||
</Progress>
|
||||
</Show>
|
||||
<Show when={progress() === undefined}>
|
||||
<div class="provider-usage-summary">
|
||||
<span>{window.label}</span>
|
||||
<span>{title()}</span>
|
||||
<strong>{value()}</strong>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={window.resetAt}>
|
||||
{(reset) => (
|
||||
<CardDescription>
|
||||
{props.language.t("profile.usage.reset", { date: new Date(reset()).toLocaleString() })}
|
||||
{props.language.t("profile.usage.reset", {
|
||||
date: new Date(reset()).toLocaleString(localeToBcp47(props.language.locale())),
|
||||
})}
|
||||
</CardDescription>
|
||||
)}
|
||||
</Show>
|
||||
@@ -153,7 +166,7 @@ const UsageCard: Component<{
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => props.onOpen(url())}
|
||||
aria-label={`Manage ${props.item.planLabel}`}
|
||||
aria-label={props.language.t("profile.usage.action.managePlan", { plan: props.item.planLabel })}
|
||||
>
|
||||
{props.language.t("profile.usage.action.manage")}
|
||||
</Button>
|
||||
@@ -208,11 +221,11 @@ const KiloPassCard: Component<{
|
||||
used={pass().currentPeriodUsageUsd}
|
||||
paid={pass().currentPeriodBaseCreditsUsd}
|
||||
bonus={pass().currentPeriodBonusCreditsUsd}
|
||||
label="This month's usage"
|
||||
paidLabel="Paid"
|
||||
label={props.language.t("profile.pass.usage")}
|
||||
paidLabel={props.language.t("profile.pass.paid")}
|
||||
bonusLabel={props.language.t("profile.pass.bonus")}
|
||||
format={money}
|
||||
aria-label="Kilo Pass monthly usage"
|
||||
aria-label={props.language.t("profile.pass.meter")}
|
||||
/>
|
||||
<Show when={renewal()}>
|
||||
{(date) => (
|
||||
@@ -261,20 +274,20 @@ export const ProviderUsageCards: Component<ProviderUsageCardsProps> = (props) =>
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={
|
||||
<Show
|
||||
when={!props.loading && props.error}
|
||||
fallback={
|
||||
<>
|
||||
<Show when={props.loading}>
|
||||
<div class="provider-usage-loading" role="status" aria-label={language.t("profile.usage.title")}>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{(error) => (
|
||||
<Card variant="warning" role="alert">
|
||||
<CardDescription>{error()}</CardDescription>
|
||||
</Card>
|
||||
)}
|
||||
</Show>
|
||||
</Show>
|
||||
<Show when={!props.loading && props.error}>
|
||||
{(error) => (
|
||||
<Card variant="warning" role="alert">
|
||||
<CardDescription>{error()}</CardDescription>
|
||||
</Card>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
|
||||
@@ -26,9 +26,7 @@ interface ServerContextValue {
|
||||
providerUsage: Accessor<ProviderUsageData | undefined>
|
||||
providerUsageLoading: Accessor<boolean>
|
||||
providerUsageError: Accessor<string | undefined>
|
||||
requestProviderUsage: () => void
|
||||
refreshProviderUsage: () => void
|
||||
releaseProviderUsage: () => void
|
||||
deviceAuth: Accessor<DeviceAuthState>
|
||||
startLogin: () => void
|
||||
goToLogin: () => void
|
||||
@@ -54,7 +52,6 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
const [providerUsage, setProviderUsage] = createSignal<ProviderUsageData>()
|
||||
const [providerUsageLoading, setProviderUsageLoading] = createSignal(false)
|
||||
const [providerUsageError, setProviderUsageError] = createSignal<string>()
|
||||
let providerUsageRetry: ReturnType<typeof setTimeout> | undefined
|
||||
const [deviceAuth, setDeviceAuth] = createSignal<DeviceAuthState>(initialDeviceAuth)
|
||||
const [vscodeLanguage, setVscodeLanguage] = createSignal<string | undefined>()
|
||||
const [languageOverride, setLanguageOverride] = createSignal<string | undefined>()
|
||||
@@ -72,12 +69,10 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
|
||||
const usageSub = vscode.onMessage((m: ExtensionMessage) => {
|
||||
if (m.type !== "providerUsageLoaded") return
|
||||
if (providerUsageRetry) clearTimeout(providerUsageRetry)
|
||||
providerUsageRetry = undefined
|
||||
if (m.reset) {
|
||||
setProviderUsage(undefined)
|
||||
setProviderUsageError(undefined)
|
||||
setProviderUsageLoading(true)
|
||||
setProviderUsageLoading(false)
|
||||
return
|
||||
}
|
||||
if (m.data) setProviderUsage(m.data)
|
||||
@@ -85,18 +80,11 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
setProviderUsageLoading(false)
|
||||
})
|
||||
|
||||
const usageReadySub = vscode.onMessage((m: ExtensionMessage) => {
|
||||
if (m.type !== "extensionDataReady" || !providerUsageLoading() || providerUsage()) return
|
||||
if (providerUsageRetry) clearTimeout(providerUsageRetry)
|
||||
providerUsageRetry = undefined
|
||||
vscode.postMessage({ type: "requestProviderUsage" })
|
||||
})
|
||||
|
||||
const resetProviderUsageForDirectory = () => {
|
||||
if (providerUsage() === undefined && !providerUsageLoading()) return
|
||||
setProviderUsage(undefined)
|
||||
setProviderUsageError(undefined)
|
||||
setProviderUsageLoading(true)
|
||||
setProviderUsageLoading(false)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -185,8 +173,6 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
gitSub()
|
||||
fontSub()
|
||||
usageSub()
|
||||
usageReadySub()
|
||||
if (providerUsageRetry) clearTimeout(providerUsageRetry)
|
||||
unsubscribe()
|
||||
})
|
||||
|
||||
@@ -218,32 +204,10 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
startLogin()
|
||||
}
|
||||
|
||||
const retryProviderUsage = () => {
|
||||
if (providerUsageRetry) clearTimeout(providerUsageRetry)
|
||||
providerUsageRetry = setTimeout(() => {
|
||||
providerUsageRetry = undefined
|
||||
if (providerUsageLoading() && !providerUsage()) vscode.postMessage({ type: "requestProviderUsage" })
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const requestProviderUsage = () => {
|
||||
setProviderUsageLoading(true)
|
||||
setProviderUsageError(undefined)
|
||||
vscode.postMessage({ type: "requestProviderUsage" })
|
||||
retryProviderUsage()
|
||||
}
|
||||
|
||||
const refreshProviderUsage = () => {
|
||||
setProviderUsageLoading(true)
|
||||
setProviderUsageError(undefined)
|
||||
vscode.postMessage({ type: "refreshProviderUsage" })
|
||||
retryProviderUsage()
|
||||
}
|
||||
|
||||
const releaseProviderUsage = () => {
|
||||
if (providerUsageRetry) clearTimeout(providerUsageRetry)
|
||||
providerUsageRetry = undefined
|
||||
vscode.postMessage({ type: "releaseProviderUsage" })
|
||||
}
|
||||
|
||||
const value: ServerContextValue = {
|
||||
@@ -257,9 +221,7 @@ export const ServerProvider: ParentComponent = (props) => {
|
||||
providerUsage,
|
||||
providerUsageLoading,
|
||||
providerUsageError,
|
||||
requestProviderUsage,
|
||||
refreshProviderUsage,
|
||||
releaseProviderUsage,
|
||||
deviceAuth,
|
||||
startLogin,
|
||||
goToLogin,
|
||||
|
||||
+14
@@ -663,6 +663,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "الخطة: تُلغى في نهاية الفترة",
|
||||
"profile.usage.plan.unknown": "الخطة: الحالة غير معروفة",
|
||||
"profile.usage.action.manage": "إدارة",
|
||||
"profile.usage.action.managePlan": "إدارة {{plan}}",
|
||||
"profile.usage.routing": "فوترة الخطة مفعّلة. توجيه Kilo Gateway {{state}}.",
|
||||
"profile.usage.routingState.disabled": "معطّل",
|
||||
"profile.usage.routingState.missing": "مفقود",
|
||||
@@ -672,6 +673,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "متبقي {{value}}",
|
||||
"profile.usage.window.remainingOf": "متبقي {{value}} من {{limit}}",
|
||||
"profile.usage.window.usedOf": "تم استخدام {{value}} من {{limit}}",
|
||||
"profile.usage.window.quota": "الحصة",
|
||||
"profile.usage.window.daily": "الحصة اليومية",
|
||||
"profile.usage.window.weekly": "الحصة الأسبوعية",
|
||||
"profile.usage.window.monthly": "الحصة الشهرية",
|
||||
"profile.usage.window.hours": "حصة كل {{count}} ساعة",
|
||||
"profile.usage.window.days": "حصة كل {{count}} يوم",
|
||||
"profile.usage.window.weeks": "حصة كل {{count}} أسبوع",
|
||||
"profile.usage.window.months": "حصة كل {{count}} شهر",
|
||||
"profile.usage.window.shared": "مشتركة",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "تتم إعادة الضبط في {{date}}",
|
||||
"profile.usage.status.unknown": "غير معروف",
|
||||
"profile.usage.status.unlimited": "غير محدود",
|
||||
@@ -681,6 +692,9 @@ export const dict = {
|
||||
"profile.action.topUp": "إضافة رصيد",
|
||||
"profile.pass.subscribe": "احصل على Kilo Pass لإضافة رصيد وكسب مكافآت",
|
||||
"profile.pass.bonus": "مكافأة",
|
||||
"profile.pass.usage": "استخدام هذا الشهر",
|
||||
"profile.pass.paid": "مدفوع",
|
||||
"profile.pass.meter": "استخدام Kilo Pass الشهري",
|
||||
"profile.pass.renews": "يتجدد",
|
||||
"profile.action.logout": "تسجيل الخروج",
|
||||
|
||||
|
||||
+14
@@ -679,6 +679,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plano: Cancela no fim do período",
|
||||
"profile.usage.plan.unknown": "Plano: Status desconhecido",
|
||||
"profile.usage.action.manage": "Gerenciar",
|
||||
"profile.usage.action.managePlan": "Gerenciar {{plan}}",
|
||||
"profile.usage.routing": "A cobrança do plano está ativa. O roteamento do Kilo Gateway está {{state}}.",
|
||||
"profile.usage.routingState.disabled": "desativado",
|
||||
"profile.usage.routingState.missing": "ausente",
|
||||
@@ -688,6 +689,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} restante",
|
||||
"profile.usage.window.remainingOf": "{{value}} de {{limit}} restantes",
|
||||
"profile.usage.window.usedOf": "{{value}} de {{limit}} usados",
|
||||
"profile.usage.window.quota": "Cota",
|
||||
"profile.usage.window.daily": "Cota diária",
|
||||
"profile.usage.window.weekly": "Cota semanal",
|
||||
"profile.usage.window.monthly": "Cota mensal",
|
||||
"profile.usage.window.hours": "Cota de {{count}} horas",
|
||||
"profile.usage.window.days": "Cota de {{count}} dias",
|
||||
"profile.usage.window.weeks": "Cota de {{count}} semanas",
|
||||
"profile.usage.window.months": "Cota de {{count}} meses",
|
||||
"profile.usage.window.shared": "Compartilhada",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Redefine em {{date}}",
|
||||
"profile.usage.status.unknown": "Desconhecido",
|
||||
"profile.usage.status.unlimited": "Ilimitado",
|
||||
@@ -697,6 +708,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Adicionar créditos",
|
||||
"profile.pass.subscribe": "Obtenha o Kilo Pass para adicionar créditos e ganhar bônus",
|
||||
"profile.pass.bonus": "Bônus",
|
||||
"profile.pass.usage": "Uso deste mês",
|
||||
"profile.pass.paid": "Pago",
|
||||
"profile.pass.meter": "Uso mensal do Kilo Pass",
|
||||
"profile.pass.renews": "Renova",
|
||||
"profile.action.logout": "Sair",
|
||||
|
||||
|
||||
+14
@@ -719,6 +719,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plan: Otkazuje se na kraju perioda",
|
||||
"profile.usage.plan.unknown": "Plan: Status nepoznat",
|
||||
"profile.usage.action.manage": "Upravljaj",
|
||||
"profile.usage.action.managePlan": "Upravljaj planom {{plan}}",
|
||||
"profile.usage.routing": "Naplata plana je aktivna. Kilo Gateway usmjeravanje je {{state}}.",
|
||||
"profile.usage.routingState.disabled": "onemogućeno",
|
||||
"profile.usage.routingState.missing": "odsutno",
|
||||
@@ -728,6 +729,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "Preostalo {{value}}",
|
||||
"profile.usage.window.remainingOf": "Preostalo {{value}} od {{limit}}",
|
||||
"profile.usage.window.usedOf": "Iskorišteno {{value}} od {{limit}}",
|
||||
"profile.usage.window.quota": "Kvota",
|
||||
"profile.usage.window.daily": "Dnevna kvota",
|
||||
"profile.usage.window.weekly": "Sedmična kvota",
|
||||
"profile.usage.window.monthly": "Mjesečna kvota",
|
||||
"profile.usage.window.hours": "Kvota od {{count}} sati",
|
||||
"profile.usage.window.days": "Kvota od {{count}} dana",
|
||||
"profile.usage.window.weeks": "Kvota od {{count}} sedmica",
|
||||
"profile.usage.window.months": "Kvota od {{count}} mjeseci",
|
||||
"profile.usage.window.shared": "Dijeljena",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Obnavlja se {{date}}",
|
||||
"profile.usage.status.unknown": "Nepoznato",
|
||||
"profile.usage.status.unlimited": "Neograničeno",
|
||||
@@ -737,6 +748,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Dopuni",
|
||||
"profile.pass.subscribe": "Nabavite Kilo Pass da dodate kredite i zaradite bonuse",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Potrošnja ovog mjeseca",
|
||||
"profile.pass.paid": "Plaćeno",
|
||||
"profile.pass.meter": "Mjesečna potrošnja Kilo Passa",
|
||||
"profile.pass.renews": "Obnavlja se",
|
||||
"profile.action.logout": "Odjava",
|
||||
|
||||
|
||||
+14
@@ -717,6 +717,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Abonnement: Opsiges ved periodens udgang",
|
||||
"profile.usage.plan.unknown": "Abonnement: Status ukendt",
|
||||
"profile.usage.action.manage": "Administrer",
|
||||
"profile.usage.action.managePlan": "Administrer {{plan}}",
|
||||
"profile.usage.routing": "Abonnementsfakturering er aktiv. Kilo Gateway-routing er {{state}}.",
|
||||
"profile.usage.routingState.disabled": "deaktiveret",
|
||||
"profile.usage.routingState.missing": "fraværende",
|
||||
@@ -726,6 +727,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} tilbage",
|
||||
"profile.usage.window.remainingOf": "{{value}} af {{limit}} tilbage",
|
||||
"profile.usage.window.usedOf": "{{value}} af {{limit}} brugt",
|
||||
"profile.usage.window.quota": "Kvote",
|
||||
"profile.usage.window.daily": "Daglig kvote",
|
||||
"profile.usage.window.weekly": "Ugentlig kvote",
|
||||
"profile.usage.window.monthly": "Månedlig kvote",
|
||||
"profile.usage.window.hours": "{{count}}-timers kvote",
|
||||
"profile.usage.window.days": "{{count}}-dages kvote",
|
||||
"profile.usage.window.weeks": "{{count}}-ugers kvote",
|
||||
"profile.usage.window.months": "{{count}}-måneders kvote",
|
||||
"profile.usage.window.shared": "Delt",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Nulstilles den {{date}}",
|
||||
"profile.usage.status.unknown": "Ukendt",
|
||||
"profile.usage.status.unlimited": "Ubegrænset",
|
||||
@@ -735,6 +746,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Fyld op",
|
||||
"profile.pass.subscribe": "Få Kilo Pass for at tilføje kredit og optjene bonusser",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Denne måneds forbrug",
|
||||
"profile.pass.paid": "Betalt",
|
||||
"profile.pass.meter": "Månedligt Kilo Pass-forbrug",
|
||||
"profile.pass.renews": "Fornyes",
|
||||
"profile.action.logout": "Log ud",
|
||||
|
||||
|
||||
@@ -730,6 +730,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Tarif: Kündigung zum Ende des Abrechnungszeitraums",
|
||||
"profile.usage.plan.unknown": "Tarif: Status unbekannt",
|
||||
"profile.usage.action.manage": "Verwalten",
|
||||
"profile.usage.action.managePlan": "{{plan}} verwalten",
|
||||
"profile.usage.routing": "Tarifabrechnung ist aktiv. Kilo-Gateway-Routing ist {{state}}.",
|
||||
"profile.usage.routingState.disabled": "deaktiviert",
|
||||
"profile.usage.routingState.missing": "nicht vorhanden",
|
||||
@@ -739,6 +740,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} verbleibend",
|
||||
"profile.usage.window.remainingOf": "{{value}} von {{limit}} verbleibend",
|
||||
"profile.usage.window.usedOf": "{{value}} von {{limit}} verwendet",
|
||||
"profile.usage.window.quota": "Kontingent",
|
||||
"profile.usage.window.daily": "Tägliches Kontingent",
|
||||
"profile.usage.window.weekly": "Wöchentliches Kontingent",
|
||||
"profile.usage.window.monthly": "Monatliches Kontingent",
|
||||
"profile.usage.window.hours": "{{count}}-Stunden-Kontingent",
|
||||
"profile.usage.window.days": "{{count}}-Tage-Kontingent",
|
||||
"profile.usage.window.weeks": "{{count}}-Wochen-Kontingent",
|
||||
"profile.usage.window.months": "{{count}}-Monats-Kontingent",
|
||||
"profile.usage.window.shared": "Geteilt",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Wird am {{date}} zurückgesetzt",
|
||||
"profile.usage.status.unknown": "Unbekannt",
|
||||
"profile.usage.status.unlimited": "Unbegrenzt",
|
||||
@@ -748,6 +759,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Aufladen",
|
||||
"profile.pass.subscribe": "Hol dir Kilo Pass, um Guthaben hinzuzufügen und Boni zu verdienen",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Verbrauch in diesem Monat",
|
||||
"profile.pass.paid": "Bezahlt",
|
||||
"profile.pass.meter": "Monatlicher Verbrauch des Kilo Pass",
|
||||
"profile.pass.renews": "Verlängert sich",
|
||||
"profile.action.logout": "Abmelden",
|
||||
|
||||
|
||||
@@ -628,6 +628,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plan: Cancels at period end",
|
||||
"profile.usage.plan.unknown": "Plan: Status unknown",
|
||||
"profile.usage.action.manage": "Manage",
|
||||
"profile.usage.action.managePlan": "Manage {{plan}}",
|
||||
"profile.usage.routing": "Plan billing is active. Kilo Gateway routing is {{state}}.",
|
||||
"profile.usage.routingState.disabled": "disabled",
|
||||
"profile.usage.routingState.missing": "missing",
|
||||
@@ -637,6 +638,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} remaining",
|
||||
"profile.usage.window.remainingOf": "{{value}} of {{limit}} remaining",
|
||||
"profile.usage.window.usedOf": "{{value}} of {{limit}} used",
|
||||
"profile.usage.window.quota": "Quota",
|
||||
"profile.usage.window.daily": "Daily quota",
|
||||
"profile.usage.window.weekly": "Weekly quota",
|
||||
"profile.usage.window.monthly": "Monthly quota",
|
||||
"profile.usage.window.hours": "{{count}}-hour quota",
|
||||
"profile.usage.window.days": "{{count}}-day quota",
|
||||
"profile.usage.window.weeks": "{{count}}-week quota",
|
||||
"profile.usage.window.months": "{{count}}-month quota",
|
||||
"profile.usage.window.shared": "Shared",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Resets {{date}}",
|
||||
"profile.usage.status.unknown": "Unknown",
|
||||
"profile.usage.status.unlimited": "Unlimited",
|
||||
@@ -646,6 +657,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Top up",
|
||||
"profile.pass.subscribe": "Get Kilo Pass to add credits and earn bonuses",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "This month's usage",
|
||||
"profile.pass.paid": "Paid",
|
||||
"profile.pass.meter": "Kilo Pass monthly usage",
|
||||
"profile.pass.renews": "Renews",
|
||||
"profile.action.logout": "Log Out",
|
||||
|
||||
|
||||
+14
@@ -724,6 +724,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plan: Se cancela al final del período",
|
||||
"profile.usage.plan.unknown": "Plan: Estado desconocido",
|
||||
"profile.usage.action.manage": "Gestionar",
|
||||
"profile.usage.action.managePlan": "Gestionar {{plan}}",
|
||||
"profile.usage.routing": "La facturación del plan está activa. El enrutamiento de Kilo Gateway está {{state}}.",
|
||||
"profile.usage.routingState.disabled": "deshabilitado",
|
||||
"profile.usage.routingState.missing": "ausente",
|
||||
@@ -733,6 +734,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} restante",
|
||||
"profile.usage.window.remainingOf": "{{value}} de {{limit}} restantes",
|
||||
"profile.usage.window.usedOf": "{{value}} de {{limit}} usados",
|
||||
"profile.usage.window.quota": "Cuota",
|
||||
"profile.usage.window.daily": "Cuota diaria",
|
||||
"profile.usage.window.weekly": "Cuota semanal",
|
||||
"profile.usage.window.monthly": "Cuota mensual",
|
||||
"profile.usage.window.hours": "Cuota de {{count}} horas",
|
||||
"profile.usage.window.days": "Cuota de {{count}} días",
|
||||
"profile.usage.window.weeks": "Cuota de {{count}} semanas",
|
||||
"profile.usage.window.months": "Cuota de {{count}} meses",
|
||||
"profile.usage.window.shared": "Compartida",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Se restablece el {{date}}",
|
||||
"profile.usage.status.unknown": "Desconocido",
|
||||
"profile.usage.status.unlimited": "Ilimitado",
|
||||
@@ -742,6 +753,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Recargar",
|
||||
"profile.pass.subscribe": "Obtén Kilo Pass para añadir créditos y ganar bonificaciones",
|
||||
"profile.pass.bonus": "Bonificación",
|
||||
"profile.pass.usage": "Uso de este mes",
|
||||
"profile.pass.paid": "Pagado",
|
||||
"profile.pass.meter": "Uso mensual de Kilo Pass",
|
||||
"profile.pass.renews": "Se renueva",
|
||||
"profile.action.logout": "Cerrar sesión",
|
||||
|
||||
|
||||
+14
@@ -631,6 +631,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "طرح: در پایان دوره لغو میشود",
|
||||
"profile.usage.plan.unknown": "طرح: وضعیت نامشخص",
|
||||
"profile.usage.action.manage": "مدیریت",
|
||||
"profile.usage.action.managePlan": "مدیریت {{plan}}",
|
||||
"profile.usage.routing": "صورتحساب طرح فعال است. مسیریابی Kilo Gateway {{state}} است.",
|
||||
"profile.usage.routingState.disabled": "غیرفعال",
|
||||
"profile.usage.routingState.missing": "ناموجود",
|
||||
@@ -640,6 +641,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} باقیمانده",
|
||||
"profile.usage.window.remainingOf": "{{value}} از {{limit}} باقیمانده",
|
||||
"profile.usage.window.usedOf": "{{value}} از {{limit}} استفادهشده",
|
||||
"profile.usage.window.quota": "سهمیه",
|
||||
"profile.usage.window.daily": "سهمیه روزانه",
|
||||
"profile.usage.window.weekly": "سهمیه هفتگی",
|
||||
"profile.usage.window.monthly": "سهمیه ماهانه",
|
||||
"profile.usage.window.hours": "سهمیه {{count}} ساعته",
|
||||
"profile.usage.window.days": "سهمیه {{count}} روزه",
|
||||
"profile.usage.window.weeks": "سهمیه {{count}} هفتهای",
|
||||
"profile.usage.window.months": "سهمیه {{count}} ماهه",
|
||||
"profile.usage.window.shared": "مشترک",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "بازنشانی در {{date}}",
|
||||
"profile.usage.status.unknown": "نامشخص",
|
||||
"profile.usage.status.unlimited": "نامحدود",
|
||||
@@ -649,6 +660,9 @@ export const dict = {
|
||||
"profile.action.topUp": "شارژ کردن",
|
||||
"profile.pass.subscribe": "Kilo Pass را دریافت کنید تا اعتبار اضافه کنید و پاداش کسب کنید",
|
||||
"profile.pass.bonus": "پاداش",
|
||||
"profile.pass.usage": "مصرف این ماه",
|
||||
"profile.pass.paid": "پرداختشده",
|
||||
"profile.pass.meter": "مصرف ماهانه Kilo Pass",
|
||||
"profile.pass.renews": "تمدید میشود",
|
||||
"profile.action.logout": "خروج",
|
||||
|
||||
|
||||
+14
@@ -730,6 +730,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Forfait : résiliation à la fin de la période",
|
||||
"profile.usage.plan.unknown": "Forfait : statut inconnu",
|
||||
"profile.usage.action.manage": "Gérer",
|
||||
"profile.usage.action.managePlan": "Gérer {{plan}}",
|
||||
"profile.usage.routing": "La facturation du forfait est active. Le routage via Kilo Gateway est {{state}}.",
|
||||
"profile.usage.routingState.disabled": "désactivé",
|
||||
"profile.usage.routingState.missing": "manquant",
|
||||
@@ -739,6 +740,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} restant",
|
||||
"profile.usage.window.remainingOf": "{{value}} sur {{limit}} restants",
|
||||
"profile.usage.window.usedOf": "{{value}} sur {{limit}} utilisés",
|
||||
"profile.usage.window.quota": "Quota",
|
||||
"profile.usage.window.daily": "Quota quotidien",
|
||||
"profile.usage.window.weekly": "Quota hebdomadaire",
|
||||
"profile.usage.window.monthly": "Quota mensuel",
|
||||
"profile.usage.window.hours": "Quota de {{count}} heures",
|
||||
"profile.usage.window.days": "Quota de {{count}} jours",
|
||||
"profile.usage.window.weeks": "Quota de {{count}} semaines",
|
||||
"profile.usage.window.months": "Quota de {{count}} mois",
|
||||
"profile.usage.window.shared": "Partagé",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Réinitialisation le {{date}}",
|
||||
"profile.usage.status.unknown": "Inconnu",
|
||||
"profile.usage.status.unlimited": "Illimité",
|
||||
@@ -748,6 +759,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Recharger",
|
||||
"profile.pass.subscribe": "Obtenez Kilo Pass pour ajouter des crédits et gagner des bonus",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Consommation de ce mois-ci",
|
||||
"profile.pass.paid": "Payé",
|
||||
"profile.pass.meter": "Consommation mensuelle du Kilo Pass",
|
||||
"profile.pass.renews": "Renouvelle",
|
||||
"profile.action.logout": "Déconnexion",
|
||||
|
||||
|
||||
+14
@@ -534,6 +534,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Piano: Si annulla al termine del periodo",
|
||||
"profile.usage.plan.unknown": "Piano: Stato sconosciuto",
|
||||
"profile.usage.action.manage": "Gestisci",
|
||||
"profile.usage.action.managePlan": "Gestisci {{plan}}",
|
||||
"profile.usage.routing": "La fatturazione del piano è attiva. L'instradamento tramite Kilo Gateway è {{state}}.",
|
||||
"profile.usage.routingState.disabled": "disabilitato",
|
||||
"profile.usage.routingState.missing": "mancante",
|
||||
@@ -543,6 +544,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} rimanente",
|
||||
"profile.usage.window.remainingOf": "{{value}} di {{limit}} rimanenti",
|
||||
"profile.usage.window.usedOf": "{{value}} di {{limit}} utilizzati",
|
||||
"profile.usage.window.quota": "Quota",
|
||||
"profile.usage.window.daily": "Quota giornaliera",
|
||||
"profile.usage.window.weekly": "Quota settimanale",
|
||||
"profile.usage.window.monthly": "Quota mensile",
|
||||
"profile.usage.window.hours": "Quota di {{count}} ore",
|
||||
"profile.usage.window.days": "Quota di {{count}} giorni",
|
||||
"profile.usage.window.weeks": "Quota di {{count}} settimane",
|
||||
"profile.usage.window.months": "Quota di {{count}} mesi",
|
||||
"profile.usage.window.shared": "Condivisa",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Si azzera il {{date}}",
|
||||
"profile.usage.status.unknown": "Sconosciuto",
|
||||
"profile.usage.status.unlimited": "Illimitato",
|
||||
@@ -552,6 +563,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Ricarica",
|
||||
"profile.pass.subscribe": "Ottieni Kilo Pass per aggiungere crediti e guadagnare bonus",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Utilizzo di questo mese",
|
||||
"profile.pass.paid": "Pagato",
|
||||
"profile.pass.meter": "Utilizzo mensile di Kilo Pass",
|
||||
"profile.pass.renews": "Si rinnova",
|
||||
"profile.action.logout": "Esci",
|
||||
"settings.agentBehaviour.title": "Comportamento agente",
|
||||
|
||||
+14
@@ -711,6 +711,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "プラン:期間終了時に解約",
|
||||
"profile.usage.plan.unknown": "プラン:ステータス不明",
|
||||
"profile.usage.action.manage": "管理",
|
||||
"profile.usage.action.managePlan": "{{plan}} を管理",
|
||||
"profile.usage.routing": "プランの請求は有効です。Kilo Gatewayのルーティングは{{state}}です。",
|
||||
"profile.usage.routingState.disabled": "無効",
|
||||
"profile.usage.routingState.missing": "欠落",
|
||||
@@ -720,6 +721,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "残り {{value}}",
|
||||
"profile.usage.window.remainingOf": "{{limit}} のうち残り {{value}}",
|
||||
"profile.usage.window.usedOf": "{{limit}} のうち {{value}} 使用済み",
|
||||
"profile.usage.window.quota": "クォータ",
|
||||
"profile.usage.window.daily": "日次クォータ",
|
||||
"profile.usage.window.weekly": "週次クォータ",
|
||||
"profile.usage.window.monthly": "月次クォータ",
|
||||
"profile.usage.window.hours": "{{count}}時間クォータ",
|
||||
"profile.usage.window.days": "{{count}}日クォータ",
|
||||
"profile.usage.window.weeks": "{{count}}週間クォータ",
|
||||
"profile.usage.window.months": "{{count}}か月クォータ",
|
||||
"profile.usage.window.shared": "共有",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "{{date}}にリセット",
|
||||
"profile.usage.status.unknown": "不明",
|
||||
"profile.usage.status.unlimited": "無制限",
|
||||
@@ -729,6 +740,9 @@ export const dict = {
|
||||
"profile.action.topUp": "チャージ",
|
||||
"profile.pass.subscribe": "Kilo Passに登録してクレジットを追加し、ボーナスを獲得",
|
||||
"profile.pass.bonus": "ボーナス",
|
||||
"profile.pass.usage": "今月の使用量",
|
||||
"profile.pass.paid": "有料分",
|
||||
"profile.pass.meter": "Kilo Pass の月間使用量",
|
||||
"profile.pass.renews": "更新",
|
||||
"profile.action.logout": "ログアウト",
|
||||
|
||||
|
||||
+14
@@ -671,6 +671,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "요금제: 기간 종료 시 취소",
|
||||
"profile.usage.plan.unknown": "요금제: 상태 알 수 없음",
|
||||
"profile.usage.action.manage": "관리",
|
||||
"profile.usage.action.managePlan": "{{plan}} 관리",
|
||||
"profile.usage.routing": "요금제 결제가 활성화되어 있습니다. Kilo Gateway 라우팅은 {{state}}입니다.",
|
||||
"profile.usage.routingState.disabled": "비활성화 상태",
|
||||
"profile.usage.routingState.missing": "누락된 상태",
|
||||
@@ -680,6 +681,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} 남음",
|
||||
"profile.usage.window.remainingOf": "{{limit}} 중 {{value}} 남음",
|
||||
"profile.usage.window.usedOf": "{{limit}} 중 {{value}} 사용됨",
|
||||
"profile.usage.window.quota": "할당량",
|
||||
"profile.usage.window.daily": "일일 할당량",
|
||||
"profile.usage.window.weekly": "주간 할당량",
|
||||
"profile.usage.window.monthly": "월간 할당량",
|
||||
"profile.usage.window.hours": "{{count}}시간 할당량",
|
||||
"profile.usage.window.days": "{{count}}일 할당량",
|
||||
"profile.usage.window.weeks": "{{count}}주 할당량",
|
||||
"profile.usage.window.months": "{{count}}개월 할당량",
|
||||
"profile.usage.window.shared": "공유",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "{{date}}에 초기화",
|
||||
"profile.usage.status.unknown": "알 수 없음",
|
||||
"profile.usage.status.unlimited": "무제한",
|
||||
@@ -689,6 +700,9 @@ export const dict = {
|
||||
"profile.action.topUp": "충전",
|
||||
"profile.pass.subscribe": "Kilo Pass를 구독하여 크레딧을 추가하고 보너스를 받으세요",
|
||||
"profile.pass.bonus": "보너스",
|
||||
"profile.pass.usage": "이번 달 사용량",
|
||||
"profile.pass.paid": "유료",
|
||||
"profile.pass.meter": "Kilo Pass 월간 사용량",
|
||||
"profile.pass.renews": "갱신",
|
||||
"profile.action.logout": "로그아웃",
|
||||
|
||||
|
||||
+14
@@ -672,6 +672,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Abonnement: Wordt aan het einde van de periode opgezegd",
|
||||
"profile.usage.plan.unknown": "Abonnement: Status onbekend",
|
||||
"profile.usage.action.manage": "Beheren",
|
||||
"profile.usage.action.managePlan": "{{plan}} beheren",
|
||||
"profile.usage.routing": "De abonnementsfacturering is actief. Kilo Gateway-routering is {{state}}.",
|
||||
"profile.usage.routingState.disabled": "uitgeschakeld",
|
||||
"profile.usage.routingState.missing": "afwezig",
|
||||
@@ -681,6 +682,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} resterend",
|
||||
"profile.usage.window.remainingOf": "{{value}} van {{limit}} resterend",
|
||||
"profile.usage.window.usedOf": "{{value}} van {{limit}} gebruikt",
|
||||
"profile.usage.window.quota": "Quotum",
|
||||
"profile.usage.window.daily": "Dagelijks quotum",
|
||||
"profile.usage.window.weekly": "Wekelijks quotum",
|
||||
"profile.usage.window.monthly": "Maandelijks quotum",
|
||||
"profile.usage.window.hours": "Quotum per {{count}} uur",
|
||||
"profile.usage.window.days": "Quotum per {{count}} dagen",
|
||||
"profile.usage.window.weeks": "Quotum per {{count}} weken",
|
||||
"profile.usage.window.months": "Quotum per {{count}} maanden",
|
||||
"profile.usage.window.shared": "Gedeeld",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Wordt op {{date}} gereset",
|
||||
"profile.usage.status.unknown": "Onbekend",
|
||||
"profile.usage.status.unlimited": "Onbeperkt",
|
||||
@@ -690,6 +701,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Opwaarderen",
|
||||
"profile.pass.subscribe": "Schaf Kilo Pass aan om tegoed toe te voegen en bonussen te verdienen",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Verbruik deze maand",
|
||||
"profile.pass.paid": "Betaald",
|
||||
"profile.pass.meter": "Maandelijks Kilo Pass-verbruik",
|
||||
"profile.pass.renews": "Vernieuwt",
|
||||
"profile.action.logout": "Uitloggen",
|
||||
|
||||
|
||||
+14
@@ -679,6 +679,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Abonnement: Avsluttes ved periodens slutt",
|
||||
"profile.usage.plan.unknown": "Abonnement: Status ukjent",
|
||||
"profile.usage.action.manage": "Administrer",
|
||||
"profile.usage.action.managePlan": "Administrer {{plan}}",
|
||||
"profile.usage.routing": "Abonnementsfakturering er aktiv. Kilo Gateway-ruting er {{state}}.",
|
||||
"profile.usage.routingState.disabled": "deaktivert",
|
||||
"profile.usage.routingState.missing": "fraværende",
|
||||
@@ -688,6 +689,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} gjenstår",
|
||||
"profile.usage.window.remainingOf": "{{value}} av {{limit}} gjenstår",
|
||||
"profile.usage.window.usedOf": "{{value}} av {{limit}} brukt",
|
||||
"profile.usage.window.quota": "Kvote",
|
||||
"profile.usage.window.daily": "Daglig kvote",
|
||||
"profile.usage.window.weekly": "Ukentlig kvote",
|
||||
"profile.usage.window.monthly": "Månedlig kvote",
|
||||
"profile.usage.window.hours": "{{count}}-timers kvote",
|
||||
"profile.usage.window.days": "{{count}}-dagers kvote",
|
||||
"profile.usage.window.weeks": "{{count}}-ukers kvote",
|
||||
"profile.usage.window.months": "{{count}}-måneders kvote",
|
||||
"profile.usage.window.shared": "Delt",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Tilbakestilles {{date}}",
|
||||
"profile.usage.status.unknown": "Ukjent",
|
||||
"profile.usage.status.unlimited": "Ubegrenset",
|
||||
@@ -697,6 +708,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Fyll på",
|
||||
"profile.pass.subscribe": "Få Kilo Pass for å legge til kreditt og tjene bonuser",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Denne månedens forbruk",
|
||||
"profile.pass.paid": "Betalt",
|
||||
"profile.pass.meter": "Månedlig Kilo Pass-forbruk",
|
||||
"profile.pass.renews": "Fornyes",
|
||||
"profile.action.logout": "Logg ut",
|
||||
|
||||
|
||||
+14
@@ -675,6 +675,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plan: Zostanie anulowany z końcem okresu",
|
||||
"profile.usage.plan.unknown": "Plan: Status nieznany",
|
||||
"profile.usage.action.manage": "Zarządzaj",
|
||||
"profile.usage.action.managePlan": "Zarządzaj {{plan}}",
|
||||
"profile.usage.routing": "Rozliczanie planu jest aktywne. Routing przez Kilo Gateway jest {{state}}.",
|
||||
"profile.usage.routingState.disabled": "wyłączony",
|
||||
"profile.usage.routingState.missing": "brakujący",
|
||||
@@ -684,6 +685,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "Pozostało {{value}}",
|
||||
"profile.usage.window.remainingOf": "Pozostało {{value}} z {{limit}}",
|
||||
"profile.usage.window.usedOf": "Wykorzystano {{value}} z {{limit}}",
|
||||
"profile.usage.window.quota": "Limit",
|
||||
"profile.usage.window.daily": "Limit dzienny",
|
||||
"profile.usage.window.weekly": "Limit tygodniowy",
|
||||
"profile.usage.window.monthly": "Limit miesięczny",
|
||||
"profile.usage.window.hours": "Limit {{count}}-godzinny",
|
||||
"profile.usage.window.days": "Limit {{count}}-dniowy",
|
||||
"profile.usage.window.weeks": "Limit {{count}}-tygodniowy",
|
||||
"profile.usage.window.months": "Limit {{count}}-miesięczny",
|
||||
"profile.usage.window.shared": "Wspólny",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Resetuje się {{date}}",
|
||||
"profile.usage.status.unknown": "Nieznany",
|
||||
"profile.usage.status.unlimited": "Bez limitu",
|
||||
@@ -693,6 +704,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Doładuj",
|
||||
"profile.pass.subscribe": "Zdobądź Kilo Pass, aby dodać środki i zdobywać bonusy",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Zużycie w tym miesiącu",
|
||||
"profile.pass.paid": "Opłacone",
|
||||
"profile.pass.meter": "Miesięczne zużycie Kilo Pass",
|
||||
"profile.pass.renews": "Odnawia się",
|
||||
"profile.action.logout": "Wyloguj się",
|
||||
|
||||
|
||||
+14
@@ -716,6 +716,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Тариф: Отмена в конце периода",
|
||||
"profile.usage.plan.unknown": "Тариф: Статус неизвестен",
|
||||
"profile.usage.action.manage": "Управлять",
|
||||
"profile.usage.action.managePlan": "Управление {{plan}}",
|
||||
"profile.usage.routing": "Оплата тарифа активна. Маршрутизация через Kilo Gateway {{state}}.",
|
||||
"profile.usage.routingState.disabled": "отключена",
|
||||
"profile.usage.routingState.missing": "отсутствует",
|
||||
@@ -725,6 +726,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "Осталось {{value}}",
|
||||
"profile.usage.window.remainingOf": "Осталось {{value}} из {{limit}}",
|
||||
"profile.usage.window.usedOf": "Использовано {{value}} из {{limit}}",
|
||||
"profile.usage.window.quota": "Квота",
|
||||
"profile.usage.window.daily": "Дневная квота",
|
||||
"profile.usage.window.weekly": "Недельная квота",
|
||||
"profile.usage.window.monthly": "Месячная квота",
|
||||
"profile.usage.window.hours": "{{count}}-часовая квота",
|
||||
"profile.usage.window.days": "{{count}}-дневная квота",
|
||||
"profile.usage.window.weeks": "{{count}}-недельная квота",
|
||||
"profile.usage.window.months": "{{count}}-месячная квота",
|
||||
"profile.usage.window.shared": "Общая",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Сбрасывается {{date}}",
|
||||
"profile.usage.status.unknown": "Неизвестно",
|
||||
"profile.usage.status.unlimited": "Без ограничений",
|
||||
@@ -734,6 +745,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Пополнить",
|
||||
"profile.pass.subscribe": "Оформите Kilo Pass, чтобы добавить кредиты и получать бонусы",
|
||||
"profile.pass.bonus": "Бонус",
|
||||
"profile.pass.usage": "Использование за этот месяц",
|
||||
"profile.pass.paid": "Оплачено",
|
||||
"profile.pass.meter": "Ежемесячное использование Kilo Pass",
|
||||
"profile.pass.renews": "Продлевается",
|
||||
"profile.action.logout": "Выйти",
|
||||
|
||||
|
||||
+14
@@ -709,6 +709,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "แผน: ยกเลิกเมื่อสิ้นสุดรอบ",
|
||||
"profile.usage.plan.unknown": "แผน: ไม่ทราบสถานะ",
|
||||
"profile.usage.action.manage": "จัดการ",
|
||||
"profile.usage.action.managePlan": "จัดการ {{plan}}",
|
||||
"profile.usage.routing": "การเรียกเก็บเงินตามแผนเปิดใช้งานอยู่ การกำหนดเส้นทาง Kilo Gateway {{state}}",
|
||||
"profile.usage.routingState.disabled": "ปิดใช้งาน",
|
||||
"profile.usage.routingState.missing": "ขาดหาย",
|
||||
@@ -718,6 +719,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "เหลือ {{value}}",
|
||||
"profile.usage.window.remainingOf": "เหลือ {{value}} จาก {{limit}}",
|
||||
"profile.usage.window.usedOf": "ใช้ไป {{value}} จาก {{limit}}",
|
||||
"profile.usage.window.quota": "โควตา",
|
||||
"profile.usage.window.daily": "โควตารายวัน",
|
||||
"profile.usage.window.weekly": "โควตารายสัปดาห์",
|
||||
"profile.usage.window.monthly": "โควตารายเดือน",
|
||||
"profile.usage.window.hours": "โควตา {{count}} ชั่วโมง",
|
||||
"profile.usage.window.days": "โควตา {{count}} วัน",
|
||||
"profile.usage.window.weeks": "โควตา {{count}} สัปดาห์",
|
||||
"profile.usage.window.months": "โควตา {{count}} เดือน",
|
||||
"profile.usage.window.shared": "ใช้ร่วมกัน",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "รีเซ็ตในวันที่ {{date}}",
|
||||
"profile.usage.status.unknown": "ไม่ทราบ",
|
||||
"profile.usage.status.unlimited": "ไม่จำกัด",
|
||||
@@ -727,6 +738,9 @@ export const dict = {
|
||||
"profile.action.topUp": "เติมเงิน",
|
||||
"profile.pass.subscribe": "รับ Kilo Pass เพื่อเพิ่มเครดิตและรับโบนัส",
|
||||
"profile.pass.bonus": "โบนัส",
|
||||
"profile.pass.usage": "การใช้งานเดือนนี้",
|
||||
"profile.pass.paid": "ชำระแล้ว",
|
||||
"profile.pass.meter": "การใช้งาน Kilo Pass รายเดือน",
|
||||
"profile.pass.renews": "ต่ออายุ",
|
||||
"profile.action.logout": "ออกจากระบบ",
|
||||
|
||||
|
||||
+14
@@ -667,6 +667,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "Plan: Dönem sonunda iptal edilecek",
|
||||
"profile.usage.plan.unknown": "Plan: Durum bilinmiyor",
|
||||
"profile.usage.action.manage": "Yönet",
|
||||
"profile.usage.action.managePlan": "{{plan}} planını yönet",
|
||||
"profile.usage.routing": "Plan faturalandırması etkin. Kilo Gateway yönlendirmesi {{state}}.",
|
||||
"profile.usage.routingState.disabled": "devre dışı",
|
||||
"profile.usage.routingState.missing": "eksik",
|
||||
@@ -676,6 +677,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "{{value}} kaldı",
|
||||
"profile.usage.window.remainingOf": "{{limit}} içinden {{value}} kaldı",
|
||||
"profile.usage.window.usedOf": "{{limit}} içinden {{value}} kullanıldı",
|
||||
"profile.usage.window.quota": "Kota",
|
||||
"profile.usage.window.daily": "Günlük kota",
|
||||
"profile.usage.window.weekly": "Haftalık kota",
|
||||
"profile.usage.window.monthly": "Aylık kota",
|
||||
"profile.usage.window.hours": "{{count}} saatlik kota",
|
||||
"profile.usage.window.days": "{{count}} günlük kota",
|
||||
"profile.usage.window.weeks": "{{count}} haftalık kota",
|
||||
"profile.usage.window.months": "{{count}} aylık kota",
|
||||
"profile.usage.window.shared": "Paylaşımlı",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "{{date}} tarihinde sıfırlanır",
|
||||
"profile.usage.status.unknown": "Bilinmiyor",
|
||||
"profile.usage.status.unlimited": "Sınırsız",
|
||||
@@ -685,6 +696,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Bakiye yükle",
|
||||
"profile.pass.subscribe": "Kredi eklemek ve bonus kazanmak için Kilo Pass edinin",
|
||||
"profile.pass.bonus": "Bonus",
|
||||
"profile.pass.usage": "Bu ayki kullanım",
|
||||
"profile.pass.paid": "Ücretli",
|
||||
"profile.pass.meter": "Aylık Kilo Pass kullanımı",
|
||||
"profile.pass.renews": "Yenilenir",
|
||||
"profile.action.logout": "Çıkış Yap",
|
||||
|
||||
|
||||
+14
@@ -669,6 +669,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "План: Скасування наприкінці періоду",
|
||||
"profile.usage.plan.unknown": "План: Статус невідомий",
|
||||
"profile.usage.action.manage": "Керувати",
|
||||
"profile.usage.action.managePlan": "Керування {{plan}}",
|
||||
"profile.usage.routing": "Оплата плану активна. Маршрутизація через Kilo Gateway {{state}}.",
|
||||
"profile.usage.routingState.disabled": "вимкнена",
|
||||
"profile.usage.routingState.missing": "відсутня",
|
||||
@@ -678,6 +679,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "Залишилося {{value}}",
|
||||
"profile.usage.window.remainingOf": "Залишилося {{value}} з {{limit}}",
|
||||
"profile.usage.window.usedOf": "Використано {{value}} з {{limit}}",
|
||||
"profile.usage.window.quota": "Квота",
|
||||
"profile.usage.window.daily": "Денна квота",
|
||||
"profile.usage.window.weekly": "Тижнева квота",
|
||||
"profile.usage.window.monthly": "Місячна квота",
|
||||
"profile.usage.window.hours": "{{count}}-годинна квота",
|
||||
"profile.usage.window.days": "{{count}}-денна квота",
|
||||
"profile.usage.window.weeks": "{{count}}-тижнева квота",
|
||||
"profile.usage.window.months": "{{count}}-місячна квота",
|
||||
"profile.usage.window.shared": "Спільна",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "Скидається {{date}}",
|
||||
"profile.usage.status.unknown": "Невідомо",
|
||||
"profile.usage.status.unlimited": "Без обмежень",
|
||||
@@ -687,6 +698,9 @@ export const dict = {
|
||||
"profile.action.topUp": "Поповнити",
|
||||
"profile.pass.subscribe": "Отримайте Kilo Pass, щоб додати кредити та заробляти бонуси",
|
||||
"profile.pass.bonus": "Бонус",
|
||||
"profile.pass.usage": "Використання за цей місяць",
|
||||
"profile.pass.paid": "Оплачено",
|
||||
"profile.pass.meter": "Щомісячне використання Kilo Pass",
|
||||
"profile.pass.renews": "Поновлюється",
|
||||
"profile.action.logout": "Вийти",
|
||||
|
||||
|
||||
+14
@@ -692,6 +692,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "套餐:将在周期结束时取消",
|
||||
"profile.usage.plan.unknown": "套餐:状态未知",
|
||||
"profile.usage.action.manage": "管理",
|
||||
"profile.usage.action.managePlan": "管理 {{plan}}",
|
||||
"profile.usage.routing": "套餐账单处于有效状态。Kilo Gateway 路由状态为 {{state}}。",
|
||||
"profile.usage.routingState.disabled": "已禁用",
|
||||
"profile.usage.routingState.missing": "缺失",
|
||||
@@ -701,6 +702,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "剩余 {{value}}",
|
||||
"profile.usage.window.remainingOf": "共 {{limit}},剩余 {{value}}",
|
||||
"profile.usage.window.usedOf": "共 {{limit}},已使用 {{value}}",
|
||||
"profile.usage.window.quota": "配额",
|
||||
"profile.usage.window.daily": "每日配额",
|
||||
"profile.usage.window.weekly": "每周配额",
|
||||
"profile.usage.window.monthly": "每月配额",
|
||||
"profile.usage.window.hours": "{{count}}小时配额",
|
||||
"profile.usage.window.days": "{{count}}天配额",
|
||||
"profile.usage.window.weeks": "{{count}}周配额",
|
||||
"profile.usage.window.months": "{{count}}个月配额",
|
||||
"profile.usage.window.shared": "共享",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "{{date}} 重置",
|
||||
"profile.usage.status.unknown": "未知",
|
||||
"profile.usage.status.unlimited": "无限制",
|
||||
@@ -710,6 +721,9 @@ export const dict = {
|
||||
"profile.action.topUp": "充值",
|
||||
"profile.pass.subscribe": "订阅 Kilo Pass 以添加额度并赚取奖励",
|
||||
"profile.pass.bonus": "奖励",
|
||||
"profile.pass.usage": "本月用量",
|
||||
"profile.pass.paid": "付费",
|
||||
"profile.pass.meter": "Kilo Pass 每月用量",
|
||||
"profile.pass.renews": "续订",
|
||||
"profile.action.logout": "退出登录",
|
||||
|
||||
|
||||
+14
@@ -652,6 +652,7 @@ export const dict = {
|
||||
"profile.usage.plan.canceling": "方案:將於週期結束時取消",
|
||||
"profile.usage.plan.unknown": "方案:狀態未知",
|
||||
"profile.usage.action.manage": "管理",
|
||||
"profile.usage.action.managePlan": "管理 {{plan}}",
|
||||
"profile.usage.routing": "方案帳單目前有效。Kilo Gateway 路由狀態為 {{state}}。",
|
||||
"profile.usage.routingState.disabled": "已停用",
|
||||
"profile.usage.routingState.missing": "缺失",
|
||||
@@ -661,6 +662,16 @@ export const dict = {
|
||||
"profile.usage.window.remaining": "剩餘 {{value}}",
|
||||
"profile.usage.window.remainingOf": "共 {{limit}},剩餘 {{value}}",
|
||||
"profile.usage.window.usedOf": "共 {{limit}},已使用 {{value}}",
|
||||
"profile.usage.window.quota": "配額",
|
||||
"profile.usage.window.daily": "每日配額",
|
||||
"profile.usage.window.weekly": "每週配額",
|
||||
"profile.usage.window.monthly": "每月配額",
|
||||
"profile.usage.window.hours": "{{count}}小時配額",
|
||||
"profile.usage.window.days": "{{count}}天配額",
|
||||
"profile.usage.window.weeks": "{{count}}週配額",
|
||||
"profile.usage.window.months": "{{count}}個月配額",
|
||||
"profile.usage.window.shared": "共享",
|
||||
"profile.usage.window.scoped": "{{resource}} · {{period}}",
|
||||
"profile.usage.reset": "{{date}} 重設",
|
||||
"profile.usage.status.unknown": "未知",
|
||||
"profile.usage.status.unlimited": "無限制",
|
||||
@@ -670,6 +681,9 @@ export const dict = {
|
||||
"profile.action.topUp": "儲值",
|
||||
"profile.pass.subscribe": "訂閱 Kilo Pass 以新增額度並賺取獎勵",
|
||||
"profile.pass.bonus": "獎勵",
|
||||
"profile.pass.usage": "本月用量",
|
||||
"profile.pass.paid": "付費",
|
||||
"profile.pass.meter": "Kilo Pass 每月用量",
|
||||
"profile.pass.renews": "續訂",
|
||||
"profile.action.logout": "登出",
|
||||
|
||||
|
||||
@@ -230,9 +230,7 @@ const chatServer = {
|
||||
providerUsage: () => undefined,
|
||||
providerUsageLoading: () => false,
|
||||
providerUsageError: () => undefined,
|
||||
requestProviderUsage: () => undefined,
|
||||
refreshProviderUsage: () => undefined,
|
||||
releaseProviderUsage: () => undefined,
|
||||
deviceAuth: () => ({ status: "idle" as const }),
|
||||
startLogin: () => undefined,
|
||||
goToLogin: () => undefined,
|
||||
|
||||
@@ -64,8 +64,8 @@ const usage: ProviderUsageData = {
|
||||
windows: [
|
||||
{
|
||||
id: "general-interval",
|
||||
label: "Shared quota 5-hour",
|
||||
resource: "general",
|
||||
period: { unit: "hour", value: 5 },
|
||||
unit: "percent",
|
||||
orientation: "remaining_percent",
|
||||
used: 24,
|
||||
|
||||
@@ -9,15 +9,6 @@ export interface ProviderUsageLoadedMessage {
|
||||
reset?: boolean
|
||||
}
|
||||
|
||||
export interface RequestProviderUsageMessage {
|
||||
type: "requestProviderUsage"
|
||||
}
|
||||
|
||||
export interface RefreshProviderUsageMessage {
|
||||
type: "refreshProviderUsage"
|
||||
}
|
||||
|
||||
/** Sent when the Profile view unmounts so the extension stops background usage refreshes. */
|
||||
export interface ReleaseProviderUsageMessage {
|
||||
type: "releaseProviderUsage"
|
||||
}
|
||||
|
||||
@@ -7,11 +7,7 @@ import type { Config } from "./config"
|
||||
import type { ModelAllocation, ReviewComment, TerminalDestination, TerminalPlacement } from "./agent-manager"
|
||||
import type { ReviewMessageData } from "../../../../src/shared/review-comments"
|
||||
import type { WorkStyle, WorkStyleState } from "../../../../src/shared/work-style-presets"
|
||||
import type {
|
||||
RefreshProviderUsageMessage,
|
||||
ReleaseProviderUsageMessage,
|
||||
RequestProviderUsageMessage,
|
||||
} from "./provider-usage"
|
||||
import type { RefreshProviderUsageMessage } from "./provider-usage"
|
||||
import type { AnacondaDesktopWebviewMessage } from "../../../../src/shared/anaconda-desktop-messages"
|
||||
import type {
|
||||
ClearLegacyDataMessage,
|
||||
@@ -1401,9 +1397,7 @@ export type WebviewMessage =
|
||||
| LoginRequest
|
||||
| LogoutRequest
|
||||
| RefreshProfileRequest
|
||||
| RequestProviderUsageMessage
|
||||
| RefreshProviderUsageMessage
|
||||
| ReleaseProviderUsageMessage
|
||||
| OpenExternalRequest
|
||||
| OpenSettingsPanelRequest
|
||||
| OpenProfilePanelRequest
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import type { ProviderUsage, ProviderUsageSnapshot } from "@kilocode/sdk/v2"
|
||||
import { formatWindow } from "@kilocode/kilo-gateway/provider-usage"
|
||||
import { formatWindow, windowLabel } from "@kilocode/kilo-gateway/provider-usage"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
@@ -26,7 +26,7 @@ function Item(props: { item: ProviderUsageSnapshot }) {
|
||||
{(window) => (
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
{window.label}: {formatWindow(window)}
|
||||
{windowLabel(window)}: {formatWindow(window)}
|
||||
</text>
|
||||
<Show when={window.resetAt}>
|
||||
{(reset) => <text fg={theme.textMuted}>Resets {new Date(reset()).toLocaleString()}</text>}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { described } from "@/server/routes/instance/httpapi/groups/metadata"
|
||||
import { ProviderUsage } from "@opencode-ai/schema/kilocode/provider-usage"
|
||||
import { AnacondaDesktopApi } from "./anaconda-desktop"
|
||||
import { ProviderUsageLocationMiddleware } from "../middleware/provider-usage-location"
|
||||
import { Result as AgentRequirementResult } from "@/kilocode/agent-requirements"
|
||||
import {
|
||||
Failure as AgentManagerFailure,
|
||||
@@ -145,28 +144,24 @@ export const KilocodeApi = HttpApi.make("kilocode")
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(ProviderUsage.Info, "Current provider usage"),
|
||||
error: HttpApiError.ServiceUnavailable,
|
||||
})
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilocode.providerUsage.get",
|
||||
summary: "Get provider usage",
|
||||
description: "Get cache-aware, secret-free provider plan usage and personal billing status.",
|
||||
}),
|
||||
)
|
||||
.middleware(ProviderUsageLocationMiddleware),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilocode.providerUsage.get",
|
||||
summary: "Get provider usage",
|
||||
description: "Get cache-aware, secret-free provider plan usage and personal billing status.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("providerUsageRefresh", KilocodePaths.providerUsageRefresh, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(ProviderUsage.Info, "Refreshed provider usage"),
|
||||
error: HttpApiError.ServiceUnavailable,
|
||||
})
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilocode.providerUsage.refresh",
|
||||
summary: "Refresh provider usage",
|
||||
description: "Refresh provider plan usage while coalescing concurrent source requests.",
|
||||
}),
|
||||
)
|
||||
.middleware(ProviderUsageLocationMiddleware),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilocode.providerUsage.refresh",
|
||||
summary: "Refresh provider usage",
|
||||
description: "Refresh provider plan usage while coalescing concurrent source requests.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("notebookList", KilocodePaths.notebookList, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Array(NotebookRequest), "Pending notebook host requests"),
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as KiloSkill from "@/kilocode/skill-remove"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Command } from "@/command"
|
||||
import { Config } from "@/config/config"
|
||||
import { WorkspaceRef } from "@/effect/instance-ref"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { HeapSnapshot } from "@/kilocode/cli/heap-snapshot"
|
||||
import type { RequestID as AgentManagerRequestID } from "@/kilocode/agent-manager/protocol"
|
||||
@@ -14,6 +15,9 @@ import type { RequestID as NotebookRequestID } from "@/kilocode/notebook/protoco
|
||||
import { Notebook } from "@/kilocode/notebook/service"
|
||||
import { ModelUsage } from "@/kilocode/session/model-usage"
|
||||
import { ProviderUsage } from "@opencode-ai/core/kilocode/provider-usage"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { InstanceHttpApi } from "@/server/routes/instance/httpapi/api"
|
||||
import { Skill } from "@/skill"
|
||||
@@ -37,6 +41,22 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
|
||||
const store = yield* InstanceStore.Service
|
||||
const manager = yield* AgentManager.Service
|
||||
const notebook = yield* Notebook.Service
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
|
||||
// Location-scoped services, keyed by the request's directory and workspace.
|
||||
const located = Effect.fnUntraced(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
|
||||
return yield* effect.pipe(
|
||||
Effect.provide(
|
||||
locations.get(
|
||||
Location.Ref.make({
|
||||
directory: AbsolutePath.make((yield* InstanceState.context).directory),
|
||||
workspaceID: yield* WorkspaceRef,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const heapSnapshot = Effect.fn("KilocodeHttpApi.heapSnapshot")(function* () {
|
||||
return yield* Effect.sync(() => HeapSnapshot.write())
|
||||
})
|
||||
@@ -108,15 +128,15 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
|
||||
})
|
||||
|
||||
const providerUsage = Effect.fn("KilocodeHttpApi.providerUsage")(function* () {
|
||||
return yield* (yield* ProviderUsage.Service)
|
||||
.get()
|
||||
.pipe(Effect.mapError(() => new HttpApiError.ServiceUnavailable({})))
|
||||
return yield* located(ProviderUsage.Service.use((usage) => usage.get())).pipe(
|
||||
Effect.mapError(() => new HttpApiError.ServiceUnavailable({})),
|
||||
)
|
||||
})
|
||||
|
||||
const providerUsageRefresh = Effect.fn("KilocodeHttpApi.providerUsageRefresh")(function* () {
|
||||
return yield* (yield* ProviderUsage.Service)
|
||||
.refresh()
|
||||
.pipe(Effect.mapError(() => new HttpApiError.ServiceUnavailable({})))
|
||||
return yield* located(ProviderUsage.Service.use((usage) => usage.refresh())).pipe(
|
||||
Effect.mapError(() => new HttpApiError.ServiceUnavailable({})),
|
||||
)
|
||||
})
|
||||
|
||||
const notebookList = Effect.fn("KilocodeHttpApi.notebookList")(function* () {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { WorkspaceRouteContext } from "@/server/routes/instance/httpapi/middleware/workspace-routing"
|
||||
|
||||
export type ProviderUsageLocationServices = Layer.Success<ReturnType<(typeof LocationServiceMap.Service)["get"]>>
|
||||
|
||||
export class ProviderUsageLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
ProviderUsageLocationMiddleware,
|
||||
{
|
||||
provides: ProviderUsageLocationServices
|
||||
requires: WorkspaceRouteContext
|
||||
}
|
||||
>()("@kilocode/HttpApiProviderUsageLocation") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
ProviderUsageLocationMiddleware,
|
||||
Effect.gen(function* () {
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
return ProviderUsageLocationMiddleware.of((effect) =>
|
||||
Effect.gen(function* () {
|
||||
const route = yield* WorkspaceRouteContext
|
||||
const directory = (() => {
|
||||
try {
|
||||
return decodeURIComponent(route.directory)
|
||||
} catch {
|
||||
return route.directory
|
||||
}
|
||||
})()
|
||||
const ref = Location.Ref.make({
|
||||
directory: AbsolutePath.make(directory),
|
||||
workspaceID: route.workspaceID,
|
||||
})
|
||||
return yield* effect.pipe(Effect.provide(locations.get(ref)))
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -29,7 +29,6 @@ import { sandboxHandlers } from "./handlers/sandbox"
|
||||
import { sessionImportHandlers } from "./handlers/session-import"
|
||||
import { suggestionHandlers } from "./handlers/suggestion"
|
||||
import { telemetryHandlers } from "./handlers/telemetry"
|
||||
import { layer as providerUsageLocationLayer } from "./middleware/provider-usage-location"
|
||||
|
||||
export const provide = Layer.provide([
|
||||
agentBuilderHandlers,
|
||||
@@ -43,7 +42,7 @@ export const provide = Layer.provide([
|
||||
instanceReloadHandlers,
|
||||
interactiveTerminalHandlers,
|
||||
kiloGatewayHandlers,
|
||||
kilocodeHandlers.pipe(Layer.provide(providerUsageLocationLayer)),
|
||||
kilocodeHandlers,
|
||||
memoryHandlers,
|
||||
networkHandlers,
|
||||
remoteHandlers,
|
||||
|
||||
@@ -10,16 +10,22 @@ export const UsageError = Schema.Struct({
|
||||
retryable: Schema.Boolean,
|
||||
}).annotate({ identifier: "ProviderUsageError" })
|
||||
|
||||
export interface UsagePeriod extends Schema.Schema.Type<typeof UsagePeriod> {}
|
||||
export const UsagePeriod = Schema.Struct({
|
||||
unit: Schema.Literals(["hour", "day", "week", "month"]),
|
||||
value: Schema.Int,
|
||||
}).annotate({ identifier: "ProviderUsagePeriod" })
|
||||
|
||||
export interface UsageWindow extends Schema.Schema.Type<typeof UsageWindow> {}
|
||||
export const UsageWindow = Schema.Struct({
|
||||
id: Schema.String,
|
||||
label: Schema.String,
|
||||
resource: Schema.String,
|
||||
unit: Schema.String,
|
||||
orientation: Schema.Literals(["used_percent", "remaining_percent", "amount", "count"]),
|
||||
used: optional(Schema.Finite),
|
||||
remaining: optional(Schema.Finite),
|
||||
limit: optional(Schema.Finite),
|
||||
period: optional(UsagePeriod),
|
||||
durationMs: optional(Schema.Finite),
|
||||
resetAt: optional(Schema.String),
|
||||
state: Schema.Literals(["active", "exhausted", "unlimited", "not_in_plan", "unknown"]),
|
||||
|
||||
@@ -4186,15 +4186,20 @@ export type CommandFile = {
|
||||
hints: Array<string>
|
||||
}
|
||||
|
||||
export type ProviderUsagePeriod = {
|
||||
unit: "hour" | "day" | "week" | "month"
|
||||
value: number
|
||||
}
|
||||
|
||||
export type ProviderUsageWindow = {
|
||||
id: string
|
||||
label: string
|
||||
resource: string
|
||||
unit: string
|
||||
orientation: "used_percent" | "remaining_percent" | "amount" | "count"
|
||||
used?: number
|
||||
remaining?: number
|
||||
limit?: number
|
||||
period?: ProviderUsagePeriod
|
||||
durationMs?: number
|
||||
resetAt?: string
|
||||
state: "active" | "exhausted" | "unlimited" | "not_in_plan" | "unknown"
|
||||
|
||||
@@ -38758,15 +38758,26 @@
|
||||
"required": ["name", "builtin", "location", "editable", "hints"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ProviderUsagePeriod": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["hour", "day", "week", "month"]
|
||||
},
|
||||
"value": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["unit", "value"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ProviderUsageWindow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"resource": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -38786,6 +38797,9 @@
|
||||
"limit": {
|
||||
"type": "number"
|
||||
},
|
||||
"period": {
|
||||
"$ref": "#/components/schemas/ProviderUsagePeriod"
|
||||
},
|
||||
"durationMs": {
|
||||
"type": "number"
|
||||
},
|
||||
@@ -38797,7 +38811,7 @@
|
||||
"enum": ["active", "exhausted", "unlimited", "not_in_plan", "unknown"]
|
||||
}
|
||||
},
|
||||
"required": ["id", "label", "resource", "unit", "orientation", "state"],
|
||||
"required": ["id", "resource", "unit", "orientation", "state"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ProviderUsageError": {
|
||||
|
||||
Reference in New Issue
Block a user