opencode-peak-scheduler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ export type Balance = {
2
+ currency: string;
3
+ total: number;
4
+ };
5
+ export declare function parseBalance(raw: unknown): Balance | null;
6
+ export declare function fetchBalance(baseURL: string, apiKey: string): Promise<Balance | null>;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ export declare const schedulerDefaults: {
3
+ readonly providerID: "deepseek";
4
+ readonly peakModel: "deepseek-v4-flash";
5
+ readonly offModel: "deepseek-v4-pro";
6
+ readonly timezone: "Asia/Shanghai";
7
+ readonly autoID: "auto";
8
+ readonly checkIntervalMs: 30000;
9
+ };
10
+ export declare const SchedulerConfigSchema: z.ZodObject<{
11
+ providerID: z.ZodDefault<z.ZodString>;
12
+ peakModel: z.ZodDefault<z.ZodString>;
13
+ offModel: z.ZodDefault<z.ZodString>;
14
+ timezone: z.ZodDefault<z.ZodString>;
15
+ autoID: z.ZodDefault<z.ZodString>;
16
+ checkIntervalMs: z.ZodDefault<z.ZodNumber>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ providerID: string;
19
+ peakModel: string;
20
+ offModel: string;
21
+ timezone: string;
22
+ autoID: string;
23
+ checkIntervalMs: number;
24
+ }, {
25
+ providerID?: string | undefined;
26
+ peakModel?: string | undefined;
27
+ offModel?: string | undefined;
28
+ timezone?: string | undefined;
29
+ autoID?: string | undefined;
30
+ checkIntervalMs?: number | undefined;
31
+ }>;
32
+ export type SchedulerConfig = z.infer<typeof SchedulerConfigSchema>;
33
+ /** Validate an options bag (e.g. the `[spec, options]` plugin tuple) into a full config. */
34
+ export declare function parseSchedulerOptions(options?: Record<string, unknown>): SchedulerConfig;
35
+ export declare function loadSchedulerConfig(path?: string): Promise<SchedulerConfig>;
@@ -0,0 +1,54 @@
1
+ export type TokenCount = {
2
+ input: number;
3
+ output: number;
4
+ reasoning: number;
5
+ cacheRead: number;
6
+ cacheWrite: number;
7
+ };
8
+ export type SessionTokens = {
9
+ input?: number;
10
+ output?: number;
11
+ reasoning?: number;
12
+ cache?: {
13
+ read?: number;
14
+ write?: number;
15
+ };
16
+ };
17
+ export type ModelPrice = {
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ };
23
+ export type PricedModel = {
24
+ cost?: {
25
+ input?: number;
26
+ output?: number;
27
+ cache?: {
28
+ read?: number;
29
+ write?: number;
30
+ };
31
+ };
32
+ };
33
+ export type ProviderLike = {
34
+ id: string;
35
+ models: Record<string, PricedModel>;
36
+ };
37
+ export declare function tokenCountFrom(tokens?: SessionTokens | null): TokenCount;
38
+ export declare function priceFrom(model?: PricedModel | null): ModelPrice | null;
39
+ export declare function computeCost(tokens: TokenCount, price: ModelPrice): number;
40
+ export declare const PEAK_PRICE_MULTIPLIER = 2;
41
+ export declare function scalePrice(price: ModelPrice | null, factor: number): ModelPrice | null;
42
+ export declare class CostTracker {
43
+ private prev;
44
+ private total;
45
+ record(now: TokenCount, price: ModelPrice | null): number;
46
+ }
47
+ export declare class CostTrackerRegistry {
48
+ private readonly trackers;
49
+ get(sessionID: string | null): CostTracker | null;
50
+ }
51
+ export declare function resolvePrices(providers: readonly ProviderLike[], providerID: string, peakModel: string, offModel: string): {
52
+ peak: ModelPrice | null;
53
+ off: ModelPrice | null;
54
+ };
@@ -0,0 +1,39 @@
1
+ import type { SchedulerConfig } from "./config";
2
+ import { type Period } from "./scheduler";
3
+ export type SessionMeta = {
4
+ providerID?: string | null;
5
+ modelID?: string | null;
6
+ };
7
+ export type EngineDelegate = {
8
+ switchModel(sessionID: string, target: {
9
+ id: string;
10
+ providerID: string;
11
+ }): Promise<boolean>;
12
+ };
13
+ export type TickResult = {
14
+ period: Period;
15
+ changed: boolean;
16
+ switched: string[];
17
+ failed: string[];
18
+ };
19
+ export declare class PeakSchedulerEngine {
20
+ private readonly cfg;
21
+ private readonly delegate;
22
+ private readonly sessions;
23
+ currentPeriod: Period | null;
24
+ constructor(cfg: SchedulerConfig, delegate: EngineDelegate);
25
+ get tracked(): string[];
26
+ track(sessionID: string, providerID?: string | null, modelID?: string | null): void;
27
+ untrack(sessionID: string): void;
28
+ noteSessionEvent(type: string, sessionID: string, providerID?: string | null, modelID?: string | null): void;
29
+ /**
30
+ * Sessions that should be (re)pointed at the auto pseudo-model so the provider
31
+ * can route by window: model-less sessions, and sessions a previous run parked
32
+ * on a concrete peak/off model. Sessions already on auto are left alone.
33
+ */
34
+ private shouldSwitch;
35
+ /** The row model that keeps routing window-aware: always the auto pseudo-model. */
36
+ private autoTarget;
37
+ ensureModel(now: Date, sessionID: string): Promise<"switched" | "skipped" | "no-period" | "failed">;
38
+ tick(now: Date): Promise<TickResult>;
39
+ }
@@ -0,0 +1,13 @@
1
+ import type { ResolvedModel } from "./scheduler";
2
+ export type Mode = "auto" | "manual";
3
+ export declare function transitionMode(current: Mode, sessionModelID: string | null, autoID: string): Mode;
4
+ export type ModeAdvance = {
5
+ mode: Mode;
6
+ prev: string | null;
7
+ };
8
+ export declare function advanceMode(prev: string | null | undefined, curr: string | null, mode: Mode, autoID: string): ModeAdvance;
9
+ export type ModeDescription = {
10
+ label: string;
11
+ model: string;
12
+ };
13
+ export declare function describeMode(mode: Mode, r: ResolvedModel): ModeDescription;
@@ -0,0 +1,7 @@
1
+ export type SchedulerOverride = {
2
+ peakModel: string;
3
+ offModel: string;
4
+ timezone: string;
5
+ autoID: string;
6
+ };
7
+ export declare function parseSchedulerOverride(raw: unknown): SchedulerOverride;
@@ -0,0 +1,27 @@
1
+ import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3GenerateResult, LanguageModelV3StreamResult } from "@ai-sdk/provider";
2
+ import { type SchedulerOverride } from "./override";
3
+ export type SchedulerLanguageModelProvider = {
4
+ languageModel(modelID: string): LanguageModelV3;
5
+ };
6
+ export type ProviderFactoryOptions = Record<string, unknown> & {
7
+ name?: string;
8
+ };
9
+ export declare function defaultInnerFactory(options: ProviderFactoryOptions): SchedulerLanguageModelProvider;
10
+ /**
11
+ * A LanguageModelV3 whose doGenerate/doStream resolve the real model by the
12
+ * Beijing window at call time, then delegate to the inner provider.
13
+ */
14
+ export declare class TimeRoutedLanguageModel implements LanguageModelV3 {
15
+ readonly specificationVersion: "v3";
16
+ readonly provider: string;
17
+ readonly modelId: string;
18
+ private readonly inner;
19
+ private readonly override;
20
+ private readonly clock;
21
+ constructor(inner: SchedulerLanguageModelProvider, provider: string, override: SchedulerOverride, clock?: () => Date);
22
+ private resolved;
23
+ get supportedUrls(): PromiseLike<Record<string, RegExp[]>> | Record<string, RegExp[]>;
24
+ doGenerate(options: LanguageModelV3CallOptions): PromiseLike<LanguageModelV3GenerateResult>;
25
+ doStream(options: LanguageModelV3CallOptions): PromiseLike<LanguageModelV3StreamResult>;
26
+ }
27
+ export declare function createSchedulerProvider(options: ProviderFactoryOptions, innerFactory?: (options: ProviderFactoryOptions) => SchedulerLanguageModelProvider): SchedulerLanguageModelProvider;
@@ -0,0 +1,15 @@
1
+ export type Period = "peak" | "off";
2
+ export type Window = "morning" | "afternoon" | null;
3
+ export type ResolvedModel = {
4
+ period: Period;
5
+ providerID: string;
6
+ modelID: string;
7
+ window: Window;
8
+ };
9
+ export type ResolveOptions = {
10
+ providerID?: string;
11
+ peakModel?: string;
12
+ offModel?: string;
13
+ timezone?: string;
14
+ };
15
+ export declare function resolveModel(date: Date, opts?: ResolveOptions): ResolvedModel;
@@ -0,0 +1,18 @@
1
+ export type SwitchModelTarget = {
2
+ id: string;
3
+ providerID: string;
4
+ };
5
+ /** The narrow slice of the v2 OpencodeClient we rely on (keeps tests mock-friendly). */
6
+ export type SwitchSessionClient = {
7
+ v2: {
8
+ session: {
9
+ switchModel(params: {
10
+ sessionID: string;
11
+ model: SwitchModelTarget;
12
+ }, options?: {
13
+ throwOnError?: boolean;
14
+ }): Promise<unknown>;
15
+ };
16
+ };
17
+ };
18
+ export declare function switchModelForSession(client: SwitchSessionClient, sessionID: string, model: SwitchModelTarget): Promise<boolean>;
@@ -0,0 +1,5 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ export type PeakSchedulerHandle = {
3
+ dispose(): void;
4
+ };
5
+ export declare function runPeakScheduler(api: TuiPluginApi, options?: Record<string, unknown>): PeakSchedulerHandle;
@@ -0,0 +1,6 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import type { ResolvedModel } from "./scheduler";
4
+ import { type Mode } from "./mode";
5
+ import type { Balance } from "./balance";
6
+ export declare function registerSidebarSlot(api: TuiPluginApi, model: () => ResolvedModel, mode: () => Mode, cost: () => number | null, balance: () => Balance | null): void;
package/dist/tui.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
2
+ export { runPeakScheduler } from "./src/tui-plugin";
3
+ declare const plugin: TuiPluginModule & {
4
+ id: string;
5
+ };
6
+ export default plugin;