dsh-opencode-go 0.1.23 → 0.2.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.
Files changed (86) hide show
  1. package/README.en.md +63 -7
  2. package/README.md +165 -76
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/assets/icon.png +0 -0
  5. package/docs/advanced.en.md +15 -22
  6. package/docs/advanced.md +15 -22
  7. package/docs/assets/logo-dark.png +0 -0
  8. package/docs/assets/logo-light.png +0 -0
  9. package/docs/development.md +47 -113
  10. package/docs/independent-package.md +4 -2
  11. package/docs/usage-analytics.en.md +108 -0
  12. package/docs/usage-analytics.md +108 -0
  13. package/docs/verification.md +25 -874
  14. package/lib/client.js +3430 -2472
  15. package/lib/index.js +2676 -2428
  16. package/lib/types/accounts.d.ts +96 -31
  17. package/lib/types/adapter.d.ts +46 -49
  18. package/lib/types/analytics.d.ts +345 -0
  19. package/lib/types/catalog.d.ts +112 -6
  20. package/lib/types/client/AnalyticsDialog.d.ts +25 -0
  21. package/lib/types/client/Section.d.ts +51 -7
  22. package/lib/types/client/UsagePill.d.ts +6 -4
  23. package/lib/types/client/controller.d.ts +176 -0
  24. package/lib/types/client/index.d.ts +9 -12
  25. package/lib/types/client/{locales.d.ts → presentation.d.ts} +124 -0
  26. package/lib/types/config.d.ts +9 -14
  27. package/lib/types/contracts.d.ts +283 -0
  28. package/lib/types/conversion.d.ts +147 -0
  29. package/lib/types/index.d.ts +7 -39
  30. package/lib/types/sdk-types.d.ts +5 -54
  31. package/lib/types/transport.d.ts +63 -0
  32. package/lib/vendor-licenses.txt +36 -36
  33. package/package.json +76 -77
  34. package/docs/fork-review-2026-09-25.md +0 -191
  35. package/docs/native-call-comparison-v020-rc2.md +0 -116
  36. package/lib/types/account-manager.d.ts +0 -25
  37. package/lib/types/account-operations.d.ts +0 -31
  38. package/lib/types/account-policy.d.ts +0 -16
  39. package/lib/types/account-recovery.d.ts +0 -2
  40. package/lib/types/account-selection.d.ts +0 -30
  41. package/lib/types/account-service.d.ts +0 -12
  42. package/lib/types/accounts-contract.d.ts +0 -38
  43. package/lib/types/call-trace.d.ts +0 -54
  44. package/lib/types/catalog-manager.d.ts +0 -20
  45. package/lib/types/client/AccountsCard.d.ts +0 -19
  46. package/lib/types/client/ModelEditor.d.ts +0 -20
  47. package/lib/types/client/accounts-controller.d.ts +0 -71
  48. package/lib/types/client/hint-mark.d.ts +0 -17
  49. package/lib/types/client/host-compat.d.ts +0 -7
  50. package/lib/types/client/host-primitives.d.ts +0 -11
  51. package/lib/types/client/section-controller.d.ts +0 -202
  52. package/lib/types/client/settings.d.ts +0 -18
  53. package/lib/types/client/staged-form.d.ts +0 -201
  54. package/lib/types/client/usage.d.ts +0 -4
  55. package/lib/types/config-contract.d.ts +0 -70
  56. package/lib/types/conversion/config.d.ts +0 -4
  57. package/lib/types/conversion/context.d.ts +0 -56
  58. package/lib/types/conversion/image-offload.d.ts +0 -13
  59. package/lib/types/conversion/image-pool.d.ts +0 -24
  60. package/lib/types/conversion/index.d.ts +0 -5
  61. package/lib/types/conversion/replay.d.ts +0 -66
  62. package/lib/types/conversion/stream.d.ts +0 -43
  63. package/lib/types/gateway-diagnostics.d.ts +0 -22
  64. package/lib/types/gateway-error.d.ts +0 -2
  65. package/lib/types/gateway-url.d.ts +0 -7
  66. package/lib/types/google-fetch.d.ts +0 -6
  67. package/lib/types/host-settings.d.ts +0 -11
  68. package/lib/types/json-response.d.ts +0 -14
  69. package/lib/types/metadata-cache.d.ts +0 -12
  70. package/lib/types/model-metadata.d.ts +0 -21
  71. package/lib/types/models-contract.d.ts +0 -66
  72. package/lib/types/models.d.ts +0 -13
  73. package/lib/types/protocol-contract.d.ts +0 -5
  74. package/lib/types/protocol-policy.d.ts +0 -4
  75. package/lib/types/provider-identity.d.ts +0 -4
  76. package/lib/types/proxy-url.d.ts +0 -2
  77. package/lib/types/proxy.d.ts +0 -11
  78. package/lib/types/reasoning.d.ts +0 -40
  79. package/lib/types/remote-contract.d.ts +0 -3
  80. package/lib/types/remotes.d.ts +0 -3
  81. package/lib/types/request-control.d.ts +0 -2
  82. package/lib/types/settings-bridge.d.ts +0 -22
  83. package/lib/types/usage-contract.d.ts +0 -35
  84. package/lib/types/usage-display.d.ts +0 -4
  85. package/lib/types/usage.d.ts +0 -27
  86. /package/lib/types/{google-generative-ai.d.ts → google.d.ts} +0 -0
@@ -1,33 +1,98 @@
1
- /** Client-safe account metadata. Secrets stay in the host credential service. */
2
- import type { GoAccountOperation } from './account-operations.ts';
3
- export type GoAccount = {
4
- id: string;
5
- name: string;
6
- apiKeyEnv: string;
7
- };
8
- export declare const MAX_ACCOUNTS = 20;
9
- export declare const ACCOUNT_REF_PREFIX = "DSH_OPENCODE_GO_ACCOUNT_";
10
- export declare const DEFAULT_ACCOUNT_REF = "OPENCODE_API_KEY";
11
- /** A credential reference is a bare environment variable name, never empty or decorated. */
12
- export declare const ACCOUNT_REF_PATTERN: RegExp;
13
- export interface AccountSettings {
14
- apiKeyEnv?: string;
15
- /** Omission/null preserves legacy credentials; an explicit empty list removes all accounts. */
16
- accounts?: readonly GoAccount[] | null;
17
- autoSwitch?: boolean;
18
- /** Recovery intentions owned by this exact settings/profile document. */
19
- accountOperations?: readonly GoAccountOperation[];
1
+ /** Serialized account commands, primary selection and official quota reads. */
2
+ import { type SettingsWriteResult, type GoAccountSwitch, type AccountCommand, type GoUsage } from './contracts.ts';
3
+ import { type CredentialProvider } from '@deepseek-ai/dsh-credentials';
4
+ import type { GoAccountSettlement } from './adapter.ts';
5
+ import { type OpencodeGoConfig } from './config.ts';
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
8
+ import { ProxyTransport } from './transport.ts';
9
+ export interface AccountSettingsWriter {
10
+ describe(): readonly {
11
+ ns: string;
12
+ revision: number;
13
+ value: unknown;
14
+ }[];
15
+ mutate(ns: string, ops: readonly {
16
+ op: 'set';
17
+ path: string[];
18
+ value: unknown;
19
+ }[], expectedRevision?: number): Promise<unknown>;
20
20
  }
21
- export declare function accountRefOf(settings: AccountSettings): string;
22
- /** Promote one account while preserving every other account's relative order. */
23
- export declare function moveAccountFirst(accounts: readonly GoAccount[], ref: string): readonly GoAccount[];
24
- /** Read legacy selections as the first account until the host persists their order. */
25
- export declare function accountsOf(settings: AccountSettings): readonly GoAccount[];
26
- export declare function assertAccounts(accounts: readonly GoAccount[] | null | undefined, selectedRef?: string): void;
27
- /** Safe UI notice for a successful request using a fallback account. */
28
- export interface GoAccountSwitch {
29
- fromRef: string;
30
- toRef: string;
31
- reason: 'quota' | 'credential';
32
- at: number;
21
+ /** Locate the profile entry that owns this mount's account metadata. */
22
+ export declare function accountSettingsRow(settings: AccountSettingsWriter): {
23
+ ns: string;
24
+ revision: number;
25
+ value: unknown;
26
+ } | undefined;
27
+ export interface AttemptOutcome {
28
+ failure?: {
29
+ code: string;
30
+ };
31
+ emitted: boolean;
32
+ totalTokens: number;
33
+ aborted: boolean;
34
+ hasNext: boolean;
33
35
  }
36
+ /** Switching accounts must never replay output or already charged work. */
37
+ export declare function accountFallback(outcome: AttemptOutcome): GoAccountSwitch['reason'] | undefined;
38
+ /** Gateway credential, rate and quota failures cool down this account for five minutes. */
39
+ export declare function isGatewayKeyRejection(failure: {
40
+ code: string;
41
+ }): boolean;
42
+ type AccountCredentials = Pick<CredentialProvider, 'describe' | 'resolve' | 'set' | 'unset'>;
43
+ /** One mount serializes account edits, credential writes and fallback promotion in memory. */
44
+ export declare class GoAccountManager {
45
+ private readonly config;
46
+ private readonly logger;
47
+ private connection;
48
+ private queue;
49
+ private generation;
50
+ private signature;
51
+ private credentialGeneration;
52
+ private latestSeq;
53
+ private notice;
54
+ constructor(config: () => OpencodeGoConfig, logger: {
55
+ warn(message: string): void;
56
+ });
57
+ connect(settings: AccountSettingsWriter, credentials: AccountCredentials): () => void;
58
+ private enqueue;
59
+ private read;
60
+ private write;
61
+ execute(raw: AccountCommand): Promise<SettingsWriteResult>;
62
+ private executeCommand;
63
+ /** A freshness fence prevents an old request from overriding a later user choice. */
64
+ capture(): number;
65
+ credentialChanged(): void;
66
+ lastSwitch(): GoAccountSwitch | undefined;
67
+ settle(event: GoAccountSettlement): void;
68
+ }
69
+ /** The browser sends commands; the host owns credentials and metadata writes. */
70
+ export declare class GoAccountsService extends TypertRemoteService {
71
+ private readonly options;
72
+ constructor(ctx: Context, options: {
73
+ manager: GoAccountManager;
74
+ });
75
+ execute(command: AccountCommand): Promise<SettingsWriteResult>;
76
+ }
77
+ interface UsageOptions {
78
+ baseURL: () => string;
79
+ proxyURL?: () => string;
80
+ transport?: ProxyTransport;
81
+ resolveApiKey: (ref?: string) => Promise<string | undefined>;
82
+ activeRef?: () => string;
83
+ accountRefs?: () => readonly string[];
84
+ lastSwitch?: () => GoAccountSwitch | undefined;
85
+ }
86
+ /** Account statistics are fetched on the Host; credentials never enter the browser. */
87
+ export declare class GoUsageService extends TypertRemoteService {
88
+ private readonly options;
89
+ private readonly identities;
90
+ private readonly pending;
91
+ private readonly transport;
92
+ constructor(ctx: Context, options: UsageOptions);
93
+ read(): Promise<GoUsage>;
94
+ readAccount(ref: string): Promise<GoUsage>;
95
+ private readRef;
96
+ private fetchUsage;
97
+ }
98
+ export {};
@@ -1,35 +1,21 @@
1
- /**
2
- * The OpenCode Go adapter: one route, one catalog, per-request routing header.
3
- *
4
- * Every request to the gateway carries two Harness-owned headers: the
5
- * attribution User-Agent (`deepseek-harness/<version>`), which pi-ai's client
6
- * lets request headers override, and `x-opencode-session`, which the gateway
7
- * requires and uses to route a conversation and share its prompt cache. The
8
- * header value is the request's session id, stable across continuation and
9
- * restore. Actual cache reads remain an upstream usage fact. A request with no
10
- * session id gets a fresh random value rather than a shared constant, because
11
- * a constant would merge unrelated traffic into one cache bucket.
12
- *
13
- * Multi-turn correctness rides on the shared pi-ai conversion machinery
14
- * (`toPiContext` reconstructs provider-native replay state from the session
15
- * log; `toStreamChunks` maps events to seam chunks), so assistant history,
16
- * tool calls, and usage land in the session log exactly as the generic pi-ai
17
- * adapter records them. Image content rides the same machinery: models whose
18
- * catalog entry declares the image modality convert attachments through the
19
- * durable attachment service, and every other model refuses image content
20
- * before any provider I/O.
21
- *
22
- * @module dsh-llm-opencode-go/adapter
23
- */
24
- import { LlmAdapter } from '@deepseek-ai/dsh-llm';
25
- import type { GenerateOptions, ImageAttachmentAccess, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
1
+ /** Request snapshots, SDK dispatch, deadlines and seamless account fallback. */
2
+ import { type OpencodeGoModelLimits, type GoAccountSwitch } from './contracts.ts';
3
+ import { type GenerateOptions, type ImageAttachmentAccess, type StreamChunk, LlmAdapter, type LlmModelInfo, type LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm';
26
4
  import type { AttachmentStore, ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
5
+ import type { Api, Model, Provider } from './sdk-types.ts';
6
+ import { type OpencodeGoConfig } from './config.ts';
7
+ import { ProxyTransport } from './transport.ts';
27
8
  import { OpencodeGoCatalog } from './catalog.ts';
28
- import { ProxyTransport } from './proxy.ts';
29
- import type { OpencodeGoConfig } from './config.ts';
30
- import { type GoAccountSwitch } from './accounts.ts';
31
- import { type GoCallTrace } from './call-trace.ts';
32
- export type { GoCallTrace } from './call-trace.ts';
9
+ /**
10
+ * Reserve 64k by default only for deepseek-v4.1-flash. Other models retain the
11
+ * SDK's default when neither the caller nor settings specify an allowance.
12
+ * Per-model settings continue to cap caller values for every model. The model
13
+ * has already had capacity overrides applied.
14
+ */
15
+ export declare function requestMaxTokens(model: {
16
+ id: string;
17
+ maxTokens: number;
18
+ }, limits: OpencodeGoModelLimits, requested?: number): number | undefined;
33
19
  /**
34
20
  * The attachment-service bridges one image request reads. Construction-time
35
21
  * (context-dependent); the config-dependent policy numbers are merged per
@@ -41,6 +27,33 @@ export interface OpencodeGoImageAccess {
41
27
  /** Bridge one attachment reference into the current model-tool execution world. */
42
28
  resolveImageAccess: (attachments: AttachmentStore, ref: ImageAttachmentRef) => ImageAttachmentAccess | undefined;
43
29
  }
30
+ /** Host dependencies used by a single attempt, independent of account selection. */
31
+ export interface OpencodeGoRequestExecutorOptions {
32
+ /** Resolve the credential reference captured with this call's endpoint; missing must fail loud. */
33
+ resolveApiKey: (config: OpencodeGoConfig, signal?: AbortSignal) => Promise<string | undefined>;
34
+ /**
35
+ * Image input machinery; absent refuses image content, which is the posture
36
+ * for direct construction without a durable attachment service behind it.
37
+ */
38
+ imageAccess?: OpencodeGoImageAccess;
39
+ /** Observe assistant history degrading to provider-neutral conversion. */
40
+ onReplayDegrade?: (reason: string) => void;
41
+ }
42
+ /** Inputs already resolved by the adapter, including one account and the wire session ID. */
43
+ export interface OpencodeGoRequestSnapshot {
44
+ config: OpencodeGoConfig;
45
+ model: Model<Api>;
46
+ provider: Provider;
47
+ sessionId: string;
48
+ }
49
+ /** Shares the adapter's transport without owning its lifetime or retrying accounts. */
50
+ export declare class OpencodeGoRequestExecutor {
51
+ private readonly options;
52
+ private readonly transport;
53
+ constructor(options: OpencodeGoRequestExecutorOptions, transport: ProxyTransport);
54
+ /** One account, one SDK attempt; host recovery still owns failures after output. */
55
+ stream(options: GenerateOptions, snapshot: OpencodeGoRequestSnapshot): AsyncIterable<StreamChunk>;
56
+ }
44
57
  /** One request's serving account, with the identity captured before discovery. */
45
58
  export interface GoAccountSettlement {
46
59
  seq: number;
@@ -50,28 +63,17 @@ export interface GoAccountSettlement {
50
63
  notice?: GoAccountSwitch;
51
64
  }
52
65
  /** Constructor inputs for {@link OpencodeGoAdapter}. */
53
- export interface OpencodeGoAdapterOptions {
66
+ export interface OpencodeGoAdapterOptions extends OpencodeGoRequestExecutorOptions {
54
67
  /** Shared with Host usage reads for this plugin mount. */
55
68
  transport?: ProxyTransport;
56
69
  /** Mount-owned discovery; direct construction supplies its own manager. */
57
70
  catalog?: (config: OpencodeGoConfig) => OpencodeGoCatalog;
58
- /** Optional directory for bounded HTTP error evidence; request contents are omitted. */
59
- debugDirectory?: () => string | undefined;
60
71
  /**
61
72
  * The current configuration, re-read at every operation: a settings write
62
73
  * reaches the next request without a restart, and one operation never mixes
63
74
  * two configuration generations.
64
75
  */
65
76
  config: () => OpencodeGoConfig;
66
- /** Resolve the credential reference captured with this call's endpoint; missing must fail loud. */
67
- resolveApiKey: (config: OpencodeGoConfig, signal?: AbortSignal) => Promise<string | undefined>;
68
- /** Observe one bounded, content-free summary for every dispatched call. */
69
- onCallTrace?: (trace: GoCallTrace) => void;
70
- /**
71
- * Image input machinery; absent refuses image content, which is the posture
72
- * for direct construction without a durable attachment service behind it.
73
- */
74
- imageAccess?: OpencodeGoImageAccess;
75
77
  /** Observe the catalog falling back to the curated table. */
76
78
  onFallback?: (detail: {
77
79
  url: string;
@@ -81,8 +83,6 @@ export interface OpencodeGoAdapterOptions {
81
83
  /** Observe live ids the curated table cannot route. */
82
84
  onOmitted?: (ids: readonly string[]) => void;
83
85
  onCatalogWarning?: (warning: string) => void;
84
- /** Observe assistant history degrading to provider-neutral conversion. */
85
- onReplayDegrade?: (reason: string) => void;
86
86
  /** Re-read picker models after a background catalog refresh commits. */
87
87
  onCatalogRefresh?: () => void;
88
88
  /** Host account-state version for actual requests, captured before asynchronous model discovery. */
@@ -101,6 +101,7 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
101
101
  private readonly options;
102
102
  private readonly resolveCatalog;
103
103
  private readonly transport;
104
+ private readonly requestExecutor;
104
105
  /** Per-reference rejection deadlines, isolated by the gateway that rejected it. */
105
106
  private readonly rejectedKeys;
106
107
  /** Reference updates invalidate rejections from attempts using an older key. */
@@ -130,10 +131,8 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
130
131
  model: LlmResolvedModelInfo;
131
132
  stream: (options: GenerateOptions) => AsyncIterable<StreamChunk>;
132
133
  }>;
133
- /** Describe one model: capacities plus the reasoning levels it actually offers. */
134
+ /** Report the same default allowance used by dispatch so the host can budget compaction. */
134
135
  private modelInfo;
135
- /** Validate an explicit effort against the model's own levels, without clamping. */
136
- private resolveReasoningLevel;
137
136
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
138
137
  private streamWithSnapshot;
139
138
  private dispatch;
@@ -141,6 +140,4 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
141
140
  private rememberRejectedKey;
142
141
  /** A stored change to a reference outranks the gateway's last rejection of it. */
143
142
  forgetRejectedKey(ref: string): void;
144
- /** One account, one SDK attempt; host recovery still owns failures after output. */
145
- private streamAttempt;
146
143
  }
@@ -0,0 +1,345 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
3
+ /**
4
+ * OpenCode Go plan snapshot: monthly limits and the one peak-pricing window the
5
+ * official tariff defines. Prices themselves stay on models.dev (the catalog's
6
+ * model.cost); this file only carries what models.dev does not publish.
7
+ *
8
+ * Source: https://opencode.ai/v2/docs/console/go/ (Go and Go Plus tables).
9
+ * The snapshot is reviewed when the model list or the plan changes; the report
10
+ * shows {@link GO_PLAN_AS_OF}, so a stale table is visible rather than silent.
11
+ *
12
+ */
13
+ /** Date the limits below were read from the official plan page. */
14
+ export declare const GO_PLAN_AS_OF = "2026-10-18";
15
+ /** Human-readable source for the limit snapshot. */
16
+ export declare const GO_PLAN_SOURCE = "https://opencode.ai/v2/docs/console/go/";
17
+ /** Why a row has no numeric limit. */
18
+ export type GoLimitNote = 'unlimited' | 'limited-time';
19
+ /** One model's monthly allowance on each plan (USD), or a note instead. */
20
+ export interface GoMonthlyLimit {
21
+ /** Go plan monthly limit in USD; null when the row is unlimited. */
22
+ readonly go: number | null;
23
+ /** Go Plus plan monthly limit in USD; null when the row is unlimited. */
24
+ readonly goPlus: number | null;
25
+ readonly note?: GoLimitNote;
26
+ }
27
+ /**
28
+ * Monthly limits by model id. Promo rows use the effective (post-strike-through)
29
+ * value and carry {@link GoLimitNote} 'limited-time'; free rows are unlimited.
30
+ */
31
+ export declare const GO_MONTHLY_LIMITS: Readonly<Record<string, GoMonthlyLimit>>;
32
+ /**
33
+ * Read one model's limit snapshot.
34
+ * @param model - catalog model id.
35
+ * @returns the row, or undefined when the plan page lists no such model.
36
+ */
37
+ export declare function goMonthlyLimit(model: string): GoMonthlyLimit | undefined;
38
+ /** Models with a documented peak/off-peak tariff (DeepSeek V4.1 Flash and V4 Pro). */
39
+ export declare const PEAK_PRICED_MODELS: ReadonlySet<string>;
40
+ /**
41
+ * Whether a request started inside the documented DeepSeek peak window:
42
+ * 01:00-04:00 and 06:00-10:00 UTC, Monday through Friday. All other hours,
43
+ * including weekends, are off-peak.
44
+ * @param at - request start time in Unix epoch milliseconds.
45
+ * @returns true when the peak multiplier applies.
46
+ */
47
+ export declare function isPeakPricedAt(at: number): boolean;
48
+ /** The four billable rates, in USD per one million tokens. */
49
+ export interface CostRates {
50
+ readonly input: number;
51
+ readonly output: number;
52
+ readonly cacheRead: number;
53
+ readonly cacheWrite: number;
54
+ }
55
+ /** One context-length tier; the highest matching threshold prices the attempt. */
56
+ export interface CostTier extends CostRates {
57
+ readonly inputTokensAbove: number;
58
+ }
59
+ /** Structural face of the catalog's ModelCost. */
60
+ export interface ModelCostLike extends CostRates {
61
+ readonly tiers?: readonly CostTier[];
62
+ }
63
+ /** The token components one request reported. */
64
+ export interface UsageCounts {
65
+ readonly input: number;
66
+ readonly output: number;
67
+ readonly cacheRead: number;
68
+ readonly cacheWrite: number;
69
+ }
70
+ /**
71
+ * Billed input tokens: reported input plus cache reads and writes.
72
+ * @param usage - one request's token components.
73
+ * @returns the total the tier threshold is compared against.
74
+ */
75
+ export declare function billedInput(usage: UsageCounts): number;
76
+ /**
77
+ * Whether every rate of a model is zero (a free row).
78
+ * @param cost - the model's catalog cost.
79
+ * @returns true when no component could ever charge.
80
+ */
81
+ export declare function isFreeCost(cost: ModelCostLike): boolean;
82
+ /**
83
+ * Price one settled request.
84
+ * @param model - the model id that served the request.
85
+ * @param usage - its token components.
86
+ * @param cost - the catalog cost for that model.
87
+ * @param at - request start time, used for the DeepSeek peak window.
88
+ * @returns the estimated cost in integer micro-USD.
89
+ */
90
+ export declare function priceUsage(model: string, usage: UsageCounts, cost: ModelCostLike, at: number): number;
91
+ /** Route id used by this plugin before 0.1.16. */
92
+ export declare const LEGACY_PROVIDER_ID = "opencode-go";
93
+ /** 0.1.16 isolated the plugin route; later legacy-id records belong to DSH native. */
94
+ export declare const LEGACY_ROUTE_CUTOFF_MS: number;
95
+ /** Minimal structural event face; the Host owns the real session types. */
96
+ export interface AnalyticsEventLike {
97
+ readonly type: string;
98
+ readonly seq: number;
99
+ readonly time: number;
100
+ readonly data?: unknown;
101
+ }
102
+ /** Minimal structural snapshot returned by readSession. */
103
+ export interface AnalyticsSnapshotLike {
104
+ readonly inheritedEventCount: number;
105
+ readonly events: readonly AnalyticsEventLike[];
106
+ }
107
+ /** One model request that the dashboard can price. */
108
+ export interface RequestFact {
109
+ /** Request start, Unix epoch milliseconds. */
110
+ readonly at: number;
111
+ readonly model: string;
112
+ readonly input: number;
113
+ readonly output: number;
114
+ readonly cacheRead: number;
115
+ readonly cacheWrite: number;
116
+ /** The request was recorded under the pre-0.1.16 route id. */
117
+ readonly legacy: boolean;
118
+ readonly kind: 'message' | 'summary';
119
+ /** The request settled without a usable usage report. */
120
+ readonly missingUsage: boolean;
121
+ }
122
+ /** Everything one session contributes, before date filtering. */
123
+ export interface FoldedSession {
124
+ readonly facts: RequestFact[];
125
+ /** Start times of attempts that committed no surface message. */
126
+ readonly attempts: number[];
127
+ /** Model calls with no usable routing header or model id. */
128
+ readonly unattributed: number[];
129
+ /** Events that passed the fork-prefix filter — never the inherited prefix (review P11). */
130
+ readonly events: number;
131
+ }
132
+ /**
133
+ * Whether a recorded provider id belongs to this plugin.
134
+ * @param provider - the provider id in a request header or summary event.
135
+ * @param at - the request start time.
136
+ * @param cutoff - legacy route cutoff, overridable in tests.
137
+ * @returns true when the request is this plugin's.
138
+ */
139
+ export declare function isPluginRoute(provider: unknown, at: number, cutoff?: number): boolean;
140
+ /**
141
+ * Fold one session snapshot.
142
+ * @param snapshot - complete raw event log plus its fork-inherited prefix length.
143
+ * @param options - test seams; the cutoff defaults to the 0.1.16 release.
144
+ * @returns the session's priceable facts and counters.
145
+ */
146
+ export declare function foldSession(snapshot: AnalyticsSnapshotLike, options?: {
147
+ cutoff?: number;
148
+ }): FoldedSession;
149
+ /** Why an analytics read failed. */
150
+ export type AnalyticsFailureReason = 'no-session-query' | 'invalid-range' | 'scan-failed';
151
+ /** One dashboard read. Dates are Unix epoch milliseconds. */
152
+ export interface AnalyticsQuery {
153
+ /** Inclusive start. */
154
+ from: number;
155
+ /** Exclusive end. */
156
+ to: number;
157
+ /** IANA timezone used to bucket days. */
158
+ timeZone: string;
159
+ /** Session scan cap, 1..2000; the Host default applies when absent. */
160
+ maxSessions?: number;
161
+ }
162
+ /** Totals over the requested range. */
163
+ export interface AnalyticsTotals {
164
+ requests: number;
165
+ unsettledAttempts: number;
166
+ summaries: number;
167
+ inputTokens: number;
168
+ outputTokens: number;
169
+ cacheReadTokens: number;
170
+ cacheWriteTokens: number;
171
+ estimatedMicroUsd: number;
172
+ pricedRequests: number;
173
+ unpricedRequests: number;
174
+ missingUsage: number;
175
+ }
176
+ /** One model's share of the range. */
177
+ export interface AnalyticsModelRow {
178
+ model: string;
179
+ name?: string;
180
+ requests: number;
181
+ missingUsage: number;
182
+ inputTokens: number;
183
+ outputTokens: number;
184
+ cacheReadTokens: number;
185
+ cacheWriteTokens: number;
186
+ estimatedMicroUsd: number;
187
+ pricedRequests: number;
188
+ unpricedRequests: number;
189
+ /** Go monthly limit in USD; null when unlimited or unknown. */
190
+ limitGoUsd: number | null;
191
+ /** Go Plus monthly limit in USD; null when unlimited or unknown. */
192
+ limitGoPlusUsd: number | null;
193
+ limitNote?: 'unlimited' | 'limited-time' | 'unknown';
194
+ }
195
+ /** One local calendar day. */
196
+ export interface AnalyticsDayRow {
197
+ /** YYYY-MM-DD in the query timezone. */
198
+ day: string;
199
+ requests: number;
200
+ tokens: number;
201
+ estimatedMicroUsd: number;
202
+ }
203
+ /** Coverage and budget limits for this report. */
204
+ export interface AnalyticsCoverage {
205
+ listedSessions: number;
206
+ scannedSessions: number;
207
+ matchedSessions: number;
208
+ failedSessions: number;
209
+ eventsScanned: number;
210
+ summaries: number;
211
+ legacyRequests: number;
212
+ missingUsage: number;
213
+ truncated: boolean;
214
+ truncation?: 'sessions' | 'events' | 'time';
215
+ scanMs: number;
216
+ timeZoneFallback?: true;
217
+ /**
218
+ * Sessions listed but never scanned because the slice cap or a budget
219
+ * stopped first (review P16/P18/M1). Observability only — this never
220
+ * implies the requested range was fully covered.
221
+ */
222
+ unreadSessions?: number;
223
+ /**
224
+ * Cache entries the end-of-run trim dropped after this report was computed
225
+ * from run-local results; the next run re-reads them (review P3/S1).
226
+ */
227
+ evictedSessions?: number;
228
+ /**
229
+ * Model calls in range with no usable routing header or model id, counted
230
+ * for visibility only — never in tokens or cost (review P12).
231
+ */
232
+ unattributedRequests?: number;
233
+ /** Session listing wall time; scanMs excludes it (review S6). */
234
+ listMs?: number;
235
+ /** Pricing harvest wall time; scanMs excludes it (review S6/P4). */
236
+ pricingMs?: number;
237
+ }
238
+ /** Where the prices came from. */
239
+ export interface AnalyticsPricing {
240
+ source: 'models.dev';
241
+ updatedAt?: number;
242
+ /**
243
+ * True only when the whole price table was unavailable; unpricedModels then
244
+ * lists every model that actually appeared in the range (review P15). A
245
+ * table that loaded but lacks a model leaves this unset and lists the model.
246
+ */
247
+ unavailable?: true;
248
+ unpricedModels: string[];
249
+ }
250
+ /** One complete dashboard response. */
251
+ export interface AnalyticsReport {
252
+ generatedAt: number;
253
+ range: {
254
+ from: number;
255
+ to: number;
256
+ };
257
+ timeZone: string;
258
+ totals: AnalyticsTotals;
259
+ byModel: AnalyticsModelRow[];
260
+ byDay: AnalyticsDayRow[];
261
+ coverage: AnalyticsCoverage;
262
+ pricing: AnalyticsPricing;
263
+ }
264
+ /** Structural face of one sessionQuery record. */
265
+ export interface AnalyticsSessionRecord {
266
+ readonly header?: {
267
+ readonly id?: string;
268
+ readonly createdAt?: number;
269
+ };
270
+ readonly live?: boolean;
271
+ readonly persisted?: boolean;
272
+ }
273
+ /** The subset of ctx.sessionQuery this service needs. */
274
+ export interface AnalyticsSessionQuery {
275
+ listSessions(signal?: AbortSignal): Promise<readonly AnalyticsSessionRecord[]>;
276
+ readSession(id: string): Promise<AnalyticsSnapshotLike>;
277
+ }
278
+ /** Structural face of one catalog model. */
279
+ export interface AnalyticsPricingModel {
280
+ readonly id?: string;
281
+ readonly name?: string;
282
+ readonly cost?: ModelCostLike;
283
+ }
284
+ /** Pricing data and its last update, when known. */
285
+ export interface AnalyticsPriceBook {
286
+ readonly models: ReadonlyMap<string, AnalyticsPricingModel>;
287
+ readonly updatedAt?: number;
288
+ }
289
+ /** Structural face of the catalog data the price book builder consumes. */
290
+ export interface AnalyticsCatalogSnapshotLike {
291
+ readonly models: ReadonlyMap<string, AnalyticsPricingModel>;
292
+ readonly details?: ReadonlyMap<string, {
293
+ readonly costKnown?: false;
294
+ }>;
295
+ readonly metadataUpdatedAtMs?: number;
296
+ }
297
+ /** Construction options; tests inject fakes for every seam. */
298
+ export interface AnalyticsOptions {
299
+ query: () => AnalyticsSessionQuery | undefined;
300
+ pricing: () => Promise<AnalyticsPriceBook | undefined>;
301
+ now?: () => number;
302
+ budgetMs?: number;
303
+ maxSessions?: number;
304
+ maxEvents?: number;
305
+ maxFacts?: number;
306
+ /**
307
+ * Maximum concurrent session reads (review P1/S3). One or less scans
308
+ * serially — the rollback path for a Host whose readSession cannot run
309
+ * concurrently.
310
+ */
311
+ maxConcurrency?: number;
312
+ }
313
+ /**
314
+ * Build the analytics price book from one catalog snapshot (review P14 core
315
+ * half). A model the metadata marked `costKnown: false` — upstream published
316
+ * no cost block, or a malformed one whose fabricated zeros are
317
+ * indistinguishable from a genuinely free model — is entered without a cost,
318
+ * so its requests land in the unpriced bucket instead of being priced at 0.
319
+ * A published block with legal components stays priced even when all zero.
320
+ */
321
+ export declare function analyticsPriceBook(snapshot: AnalyticsCatalogSnapshotLike): AnalyticsPriceBook;
322
+ /** One mount owns the scan cache; multiple dashboard opens share it. */
323
+ export declare class GoAnalyticsService extends TypertRemoteService {
324
+ private readonly options;
325
+ private readonly cache;
326
+ private readonly pending;
327
+ /** The most recent successful price table; stale-on-error fallback (review P4/S4). */
328
+ private lastPriceBook;
329
+ private factTotal;
330
+ constructor(ctx: Context, options: AnalyticsOptions);
331
+ /**
332
+ * Read one aggregate report.
333
+ * @param raw - the validated query from the RPC codec.
334
+ * @param signal - caller cancellation, injected by the gateway after the
335
+ * business parameters and never carried on the wire.
336
+ * @returns the report; failures carry a reason in the domain error details.
337
+ */
338
+ read(raw: AnalyticsQuery, signal?: AbortSignal): Promise<AnalyticsReport>;
339
+ private now;
340
+ private limitOf;
341
+ private run;
342
+ private remember;
343
+ /** The one eviction point; returns how many entries it dropped (review S1/P2). */
344
+ private trim;
345
+ }