@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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/NOTICE +15 -0
- package/README.md +268 -2
- package/README.ru.md +87 -0
- package/README.zh.md +87 -0
- package/cordis.patch.yml +11 -0
- package/lib/client.js +2739 -0
- package/lib/index.d.ts +380 -0
- package/lib/index.js +5078 -0
- package/media/visual-verification.png +0 -0
- package/package.json +123 -4
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
|