@goodandready/dsh-opencode-provider 0.0.0-stage → 0.5.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.
package/lib/index.d.ts ADDED
@@ -0,0 +1,380 @@
1
+ import { GenerateOptions, LlmAdapter, LlmError, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from "@deepseek-ai/dsh-llm";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { Context } from "@deepseek-ai/cordis";
4
+ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
5
+ import { WebServer } from "@deepseek-ai/dsh-host-webserver";
6
+ //#region src/config.d.ts
7
+ type TransportId = 'openai-chat' | 'openai-responses' | 'anthropic' | 'google';
8
+ interface AccountConfig {
9
+ label?: string;
10
+ apiKeyEnv?: string;
11
+ apiKey?: string;
12
+ }
13
+ interface ModelAccountRule {
14
+ pattern: string;
15
+ account: string;
16
+ }
17
+ /**
18
+ * Plugin configuration. Every field except the literal `apiKey` is volatile:
19
+ * the harness projects settings forms from schema fields carrying that marker.
20
+ */
21
+ interface Config {
22
+ /** Credential reference resolved per request; defaults to `OPENCODE_API_KEY`. */
23
+ apiKeyEnv?: string;
24
+ /** Literal key for composition config only; stripped from settings reads by `role('secret')`. */
25
+ apiKey?: string;
26
+ /** Console/Zen inference root; defaults to `https://opencode.ai/inference`. */
27
+ apiBase?: string;
28
+ /** Go inference root; defaults to `https://opencode.ai/inference/go`. */
29
+ goApiBase?: string;
30
+ /** Go unified gateway root; defaults to `https://opencode.ai/zen/go/v1`. */
31
+ goZenBase?: string;
32
+ /** Go usage endpoint; defaults to `https://opencode.ai/zen/go/v1/usage`. */
33
+ usageUrl?: string;
34
+ /** Entry in OpenCode's `auth.json` to import; defaults to `opencode-go`. */
35
+ openCodeAuthEntry?: string;
36
+ /** Milliseconds to wait for response headers; defaults to 300s. */
37
+ requestTimeoutMs?: number;
38
+ /** Milliseconds a stream may stall; defaults to 300s. */
39
+ streamIdleTimeoutMs?: number;
40
+ /** Visible-model allowlist; an empty list means "show everything". */
41
+ visibleModels?: string[];
42
+ /** Per-model visibility overrides; wins over `visibleModels`. */
43
+ modelVisibility?: Record<string, boolean>;
44
+ /** Hide models outside the Go allowance on the Go route; defaults to true. */
45
+ filterModelsByPlan?: boolean;
46
+ /** Per-model transport overrides; wins over the generated metadata. */
47
+ modelTransports?: Record<string, TransportId>;
48
+ /** Global context-window override. */
49
+ contextWindow?: number;
50
+ /** Per-model context-window overrides. */
51
+ contextWindowOverrides?: Record<string, number>;
52
+ /** Per-turn output cap; `0` omits `max_tokens`. */
53
+ maxOutputTokens?: number;
54
+ /** Extra accounts for rotation (parsed now, used by the account pool later). */
55
+ accounts?: AccountConfig[];
56
+ /** Preferred account per model glob; other accounts remain fallbacks. */
57
+ modelAccountRules?: ModelAccountRule[];
58
+ /** Pinned account id; empty means automatic. */
59
+ activeAccount?: string;
60
+ /** Show the quota card in the sidebar. */
61
+ showSidebarQuota?: boolean;
62
+ /** Product user agent sent to OpenCode. */
63
+ userAgent?: string;
64
+ }
65
+ declare const Config: z<Config>;
66
+ interface ResolvedConfig {
67
+ apiKeyEnv: string;
68
+ apiKey?: string;
69
+ apiBase: string;
70
+ goApiBase: string;
71
+ goZenBase: string;
72
+ usageUrl: string;
73
+ openCodeAuthEntry: string;
74
+ requestTimeoutMs: number;
75
+ streamIdleTimeoutMs: number;
76
+ visibleModels: readonly string[];
77
+ modelVisibility: Readonly<Record<string, boolean>>;
78
+ filterModelsByPlan: boolean;
79
+ modelTransports: Readonly<Record<string, TransportId>>;
80
+ contextWindow?: number;
81
+ contextWindowOverrides: Readonly<Record<string, number>>;
82
+ maxOutputTokens?: number;
83
+ accounts: readonly AccountConfig[];
84
+ modelAccountRules: readonly ModelAccountRule[];
85
+ activeAccount: string;
86
+ showSidebarQuota: boolean;
87
+ userAgent: string;
88
+ }
89
+ //#endregion
90
+ //#region src/credentials.d.ts
91
+ interface ResolvedKey {
92
+ readonly key: string;
93
+ readonly source: string;
94
+ /** Slot label (`default` or the configured account name). */
95
+ readonly label?: string;
96
+ }
97
+ type KeyResolver = (provider: string) => Promise<ResolvedKey>;
98
+ interface KeyPool {
99
+ resolve(provider: string): Promise<ResolvedKey>;
100
+ resolveAll(provider: string): Promise<readonly ResolvedKey[]>;
101
+ }
102
+ /** OpenCode stores its own sign-in at this XDG data path. */
103
+ declare function openCodeAuthPath(): string;
104
+ /**
105
+ * Resolve every configured account for one request. The default account comes
106
+ * first unless `activeAccount` pins another slot by label or credential name.
107
+ * Keys are deduplicated; a slot without a resolvable value is skipped.
108
+ */
109
+ declare function createKeyPool(ctx: Context, config: () => ResolvedConfig): KeyPool;
110
+ /** Backwards-compatible single-key facade over the pool. */
111
+ declare function createKeyResolver(ctx: Context, config: () => ResolvedConfig): KeyResolver;
112
+ //#endregion
113
+ //#region src/adapter.d.ts
114
+ interface OpenCodeAdapterDeps {
115
+ readonly config: () => ResolvedConfig;
116
+ readonly resolveKey: KeyResolver;
117
+ readonly resolveKeys?: (provider: string) => Promise<readonly ResolvedKey[]>;
118
+ readonly resolveAttachments?: () => AttachmentStore | undefined;
119
+ readonly fetchImpl?: typeof fetch;
120
+ /** Test seam; defaults to the local append-only request log. */
121
+ readonly recordRequest?: (provider: string, model: string, purpose?: string) => void;
122
+ /** Test seam; defaults to ~/.dsh/storages/dsh-opencode/cooldowns.json. */
123
+ readonly cooldownPath?: string;
124
+ /** Receives a one-time warning when the provider tool limit drops tools. */
125
+ readonly warn?: (message: string) => void;
126
+ }
127
+ declare class OpenCodeAdapter extends LlmAdapter {
128
+ private readonly deps;
129
+ private readonly fetchImpl;
130
+ private readonly cooldownPath;
131
+ private cooldownWrites;
132
+ /** key fingerprint → cooldown deadline and the rejection that caused it. */
133
+ private readonly cooldowns;
134
+ private warnedTools;
135
+ constructor(deps: OpenCodeAdapterDeps);
136
+ providerInfo(provider: string): LlmProviderInfo;
137
+ providerRetryPolicy(_provider: string): ResolvedRetryPolicy;
138
+ listModels(provider: string, opts?: {
139
+ unfiltered?: boolean;
140
+ }): Promise<readonly LlmModelInfo[]>;
141
+ resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
142
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
143
+ private run;
144
+ private parse;
145
+ private send;
146
+ private keyId;
147
+ private coolingDown;
148
+ /** Current cooldown for one resolved key, if any. */
149
+ accountHealth(key: string): {
150
+ coolingUntil: number;
151
+ reason: string;
152
+ } | undefined;
153
+ /** Await pending cooldown persistence writes (tests and shutdown). */
154
+ flushCooldowns(): Promise<void>;
155
+ private loadCooldowns;
156
+ private pruneCooldowns;
157
+ private persistCooldowns;
158
+ private markCooldown;
159
+ private isTransient;
160
+ }
161
+ //#endregion
162
+ //#region src/usage.d.ts
163
+ interface UsageWindow {
164
+ readonly percentUsed: number;
165
+ readonly resetsAt?: string;
166
+ readonly status?: string;
167
+ }
168
+ interface UsageReport {
169
+ readonly windows: {
170
+ readonly fiveHour?: UsageWindow;
171
+ readonly weekly?: UsageWindow;
172
+ readonly monthly?: UsageWindow;
173
+ };
174
+ }
175
+ interface UsageResult {
176
+ readonly report?: UsageReport;
177
+ readonly error?: string;
178
+ readonly checkedAt: number;
179
+ }
180
+ interface UsageWindowPayload {
181
+ status?: string;
182
+ percent?: number;
183
+ resetsAt?: string;
184
+ }
185
+ interface UsagePayload {
186
+ usage?: {
187
+ rolling?: UsageWindowPayload;
188
+ weekly?: UsageWindowPayload;
189
+ monthly?: UsageWindowPayload;
190
+ };
191
+ }
192
+ declare function normalizeUsage(payload: UsagePayload): UsageReport;
193
+ interface UsageReader {
194
+ (provider: string, key?: string): Promise<UsageResult | undefined>;
195
+ /** Drop the cache so the next read hits the endpoint. */
196
+ clear(): void;
197
+ }
198
+ /** Cached reader for the (undocumented) Go usage endpoint. Fail-open by design. */
199
+ declare function createUsageReader(config: () => ResolvedConfig, resolveKey: KeyResolver, fetchImpl?: typeof fetch): UsageReader;
200
+ //#endregion
201
+ //#region src/commands.d.ts
202
+ interface CommandDeps {
203
+ readonly config: () => ResolvedConfig;
204
+ readonly pool: KeyPool;
205
+ readonly usage: (provider: string, key?: string) => Promise<UsageResult | undefined>;
206
+ readonly adapter: OpenCodeAdapter;
207
+ }
208
+ declare function applyCommands(ctx: Context, deps: CommandDeps): void;
209
+ //#endregion
210
+ //#region src/analytics.d.ts
211
+ interface ModelUsageRow {
212
+ provider: string;
213
+ model: string;
214
+ sessions: number;
215
+ turns: number;
216
+ steps: number;
217
+ inputTokens: number;
218
+ outputTokens: number;
219
+ cacheReadTokens: number;
220
+ cacheWriteTokens: number;
221
+ cost: number;
222
+ priced: boolean;
223
+ partial: boolean;
224
+ free: boolean;
225
+ llmMs: number;
226
+ ttftMs: number;
227
+ ttftSteps: number;
228
+ decodeMs: number;
229
+ }
230
+ interface AnalyticsSummary {
231
+ sessions: number;
232
+ requests: number;
233
+ tokens: number;
234
+ inputTokens: number;
235
+ outputTokens: number;
236
+ reasoningTokens: number | null;
237
+ cacheReadTokens: number;
238
+ cacheWriteTokens: number;
239
+ cost: number;
240
+ priced: boolean;
241
+ partial: boolean;
242
+ free: boolean;
243
+ avgTtftMs: number | null;
244
+ avgLatencyMs: number | null;
245
+ errors: number | null;
246
+ successRate: number | null;
247
+ }
248
+ interface DayUsageRow {
249
+ date: string;
250
+ sessions: number;
251
+ requests: number;
252
+ tokens: number;
253
+ cost: number;
254
+ priced: boolean;
255
+ partial: boolean;
256
+ free: boolean;
257
+ avgTtftMs: number | null;
258
+ }
259
+ interface SessionUsageRow {
260
+ id: string;
261
+ provider: string;
262
+ model: string;
263
+ at: number;
264
+ requests: number;
265
+ tokens: number;
266
+ inputTokens: number;
267
+ outputTokens: number;
268
+ cacheReadTokens: number;
269
+ cacheWriteTokens: number;
270
+ cost: number;
271
+ priced: boolean;
272
+ partial: boolean;
273
+ free: boolean;
274
+ avgTtftMs: number | null;
275
+ avgLatencyMs: number | null;
276
+ }
277
+ interface AnalyticsReport {
278
+ since: number;
279
+ scannedSessions: number;
280
+ matchedSessions: number;
281
+ models: ModelUsageRow[];
282
+ summary: AnalyticsSummary;
283
+ days: DayUsageRow[];
284
+ sessions: SessionUsageRow[];
285
+ }
286
+ //#endregion
287
+ //#region src/request-log.d.ts
288
+ interface ModelRequestHeatmap {
289
+ provider: string;
290
+ model: string;
291
+ total: number;
292
+ hours: number[];
293
+ grid: number[][];
294
+ }
295
+ interface RequestHeatmap {
296
+ since: number;
297
+ total: number;
298
+ /** Requests per hour of day, local time. */
299
+ hours: number[];
300
+ /** Monday-first weekday × hour grid, local time. */
301
+ grid: number[][];
302
+ firstAt: number | null;
303
+ lastAt: number | null;
304
+ models: ModelRequestHeatmap[];
305
+ }
306
+ //#endregion
307
+ //#region src/routes.d.ts
308
+ interface RouteDeps {
309
+ readonly config: () => ResolvedConfig;
310
+ readonly adapter: OpenCodeAdapter;
311
+ readonly pool: KeyPool;
312
+ readonly usage: UsageReader;
313
+ readonly analytics?: (days: number) => Promise<AnalyticsReport>;
314
+ readonly heatmap?: (days: number) => Promise<RequestHeatmap>;
315
+ readonly refreshTui?: () => void;
316
+ }
317
+ /** Register the Host HTTP surface consumed by the browser settings page. */
318
+ declare function applyRoutes(ctx: Context, web: WebServer, deps: RouteDeps): void;
319
+ //#endregion
320
+ //#region src/session-id.d.ts
321
+ /**
322
+ * A stable, opaque session id for `x-opencode-session`. The header must stay
323
+ * identical across every turn and retry of one conversation; deriving it from
324
+ * the durable session id (or, for hand-built calls, the first user turn) gives
325
+ * that stability without storing state. OpenCode only treats it as an opaque
326
+ * routing key, so the `dsh_` prefix is safe.
327
+ */
328
+ declare function openCodeSessionId(options: GenerateOptions): string;
329
+ //#endregion
330
+ //#region src/errors.d.ts
331
+ /**
332
+ * Translate an OpenCode HTTP failure into the harness failure taxonomy.
333
+ * OpenCode reports several distinct conditions as 400/401, so the body type
334
+ * and message decide the code — never the status alone.
335
+ */
336
+ declare function classifyResponseError(status: number, text: string, headers: Headers): LlmError;
337
+ //#endregion
338
+ //#region src/models.generated.d.ts
339
+ type OpenCodeTransport = 'openai-chat' | 'openai-responses' | 'anthropic' | 'google';
340
+ interface OpenCodeModelMeta {
341
+ readonly id: string;
342
+ readonly name: string;
343
+ readonly transport: OpenCodeTransport;
344
+ readonly contextWindow: number;
345
+ readonly maxTokens: number;
346
+ readonly input: readonly ('text' | 'image')[];
347
+ readonly reasoning: readonly string[];
348
+ readonly free: boolean;
349
+ readonly cost: {
350
+ readonly input: number;
351
+ readonly output: number;
352
+ readonly cacheRead: number;
353
+ };
354
+ }
355
+ //#endregion
356
+ //#region src/catalog.d.ts
357
+ interface EffectiveModel extends OpenCodeModelMeta {
358
+ readonly transport: OpenCodeTransport;
359
+ readonly contextWindow: number;
360
+ }
361
+ declare function effectiveModel(id: string, config: ResolvedConfig): EffectiveModel;
362
+ declare function describeModel(meta: EffectiveModel, provider?: string): string;
363
+ declare function clearCatalogCache(): void;
364
+ //#endregion
365
+ //#region src/index.d.ts
366
+ declare const name = "llm-opencode";
367
+ declare const inject: string[];
368
+ /**
369
+ * Register both provider routes against the dsh `llm` service:
370
+ *
371
+ * - `opencode` — Console / Zen pay-as-you-go.
372
+ * - `opencode-go` — Go / Go Plus subscription (same API, Go base path, quotas).
373
+ *
374
+ * Requests resolve their key per call, so a changed credential reaches the
375
+ * next request without a restart.
376
+ */
377
+ declare function apply(ctx: Context, config: Config): void;
378
+ //#endregion
379
+ export { type CommandDeps, Config, type Config as OpenCodeConfig, OpenCodeAdapter, type OpenCodeAdapterDeps, type ResolvedConfig, type ResolvedKey, type RouteDeps, type TransportId, type UsageReport, type UsageResult, type UsageWindow, apply, applyCommands, applyRoutes, classifyResponseError, clearCatalogCache, createKeyPool, createKeyResolver, createUsageReader, describeModel, effectiveModel, inject, name, normalizeUsage, openCodeAuthPath, openCodeSessionId };
380
+ //# sourceMappingURL=index.d.ts.map