dsh-workbuddy-connect 0.2.1

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,332 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
3
+ import { Context } from "@deepseek-ai/cordis";
4
+ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
5
+ //#region src/upstream.d.ts
6
+ /** WorkBuddy region selected by the credential's login domain. */
7
+ type WorkBuddyRegion = 'cn' | 'global';
8
+ /** Upstream failure classes the shim maps onto distinct HTTP answers. */
9
+ type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
10
+ /** One CLI-usable model as the upstream catalog describes it. */
11
+ interface WorkBuddyUpstreamModel {
12
+ id: string;
13
+ name: string;
14
+ contextWindow: number;
15
+ maxTokens: number;
16
+ }
17
+ /** One billing package and its remaining credit. */
18
+ interface WorkBuddyCreditAccount {
19
+ packageName: string;
20
+ remain: number;
21
+ size: number;
22
+ }
23
+ /** Aggregated credit answer for one credential. */
24
+ interface WorkBuddyCredits {
25
+ total: number;
26
+ accounts: readonly WorkBuddyCreditAccount[];
27
+ }
28
+ /** Token refresh answer; fields the upstream omits stay absent. */
29
+ interface WorkBuddyRefreshOutcome {
30
+ accessToken: string;
31
+ refreshToken?: string;
32
+ expiresInSec?: number;
33
+ domain?: string;
34
+ }
35
+ /** Chat answer: either a live SSE response or a classified failure. */
36
+ type WorkBuddyChatResult = {
37
+ ok: true;
38
+ response: Response;
39
+ } | {
40
+ ok: false;
41
+ status: number;
42
+ kind: UpstreamErrorKind;
43
+ message: string;
44
+ };
45
+ /** Classify an upstream failure from its HTTP status and body excerpt. */
46
+ declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
47
+ /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
48
+ declare function regionOf(domain: string): WorkBuddyRegion;
49
+ /**
50
+ * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
51
+ * force `stream: true` (the upstream rejects non-streaming) and flatten
52
+ * `tool_choice` (the upstream's field is a string; object forms return 400).
53
+ */
54
+ declare function prepareChatBody(source: string): string;
55
+ /**
56
+ * Upstream HTTP client. One instance serves the whole plugin; requests take
57
+ * the credential explicitly so token refreshes apply on the next call.
58
+ */
59
+ declare class WorkBuddyUpstreamClient {
60
+ /** POST the chat endpoint; a successful answer is the raw SSE response. */
61
+ chatStream(credential: WorkBuddyCredential, bodyJson: string, signal?: AbortSignal): Promise<WorkBuddyChatResult>;
62
+ /** POST the token-refresh endpoint; the caller merges the outcome. */
63
+ refreshToken(credential: WorkBuddyCredential): Promise<WorkBuddyRefreshOutcome>;
64
+ /** GET the personal model catalog and keep the `cli` agent's models only. */
65
+ fetchModels(credential: WorkBuddyCredential): Promise<readonly WorkBuddyUpstreamModel[]>;
66
+ /** POST the billing endpoint for the aggregated remaining credit. */
67
+ fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
68
+ }
69
+ //#endregion
70
+ //#region src/auth.d.ts
71
+ /** Normalized WorkBuddy credential, timestamps in epoch milliseconds. */
72
+ interface WorkBuddyCredential {
73
+ accessToken: string;
74
+ refreshToken: string;
75
+ expiresAtMs: number;
76
+ refreshExpiresAtMs?: number;
77
+ domain: string;
78
+ uid: string;
79
+ enterpriseId?: string;
80
+ nickname?: string;
81
+ /** Which storage the credential was read from; refreshes are always `dsh`. */
82
+ source: 'desktop' | 'dsh';
83
+ }
84
+ /** Read-only sign-in summary for status and doctor output. */
85
+ interface WorkBuddyAuthStatus {
86
+ state: 'signed-in' | 'signed-out';
87
+ expiresAtMs?: number;
88
+ refreshExpiresAtMs?: number;
89
+ nickname?: string;
90
+ domain?: string;
91
+ source?: 'desktop' | 'dsh';
92
+ }
93
+ /** Constructor options; only {@link refresh} is required. */
94
+ interface WorkBuddyStoreOptions {
95
+ /** Explicit desktop auth-file path, overriding env and platform defaults. */
96
+ desktopPath?: string;
97
+ /** Explicit plugin-owned copy path, defaulting under `$DSH_HOME`. */
98
+ ownPath?: string;
99
+ /** Performs the upstream token refresh. */
100
+ refresh: (credential: WorkBuddyCredential) => Promise<WorkBuddyRefreshOutcome>;
101
+ /** Refresh this long before actual expiry; default five minutes. */
102
+ refreshMarginMs?: number;
103
+ }
104
+ /** Basename of the plugin-owned credential copy inside the Harness home. */
105
+ declare const WORKBUDDY_AUTH_FILENAME = ".workbuddy-auth.json";
106
+ /** Env variable that overrides the desktop auth-file location. */
107
+ declare const WORKBUDDY_AUTH_FILE_ENV = "WORKBUDDY_AUTH_FILE";
108
+ /** Plugin-owned copy path inside the Harness home. */
109
+ declare function workbuddyOwnAuthPath(): string;
110
+ /** Platform default for the WorkBuddy desktop app's auth file. */
111
+ declare function defaultDesktopAuthPath(): string | undefined;
112
+ /**
113
+ * Parse a WorkBuddy auth document in either on-disk shape: the plugin OAuth
114
+ * nested form `{"auth":{...},"account":{...}}` and the flat panel form.
115
+ * Returns undefined when the document carries no access token.
116
+ */
117
+ declare function parseWorkBuddyAuth(text: string): WorkBuddyCredential | undefined;
118
+ /**
119
+ * Read-only credential store with demand-driven refresh.
120
+ *
121
+ * Refresh policy: refresh only when the access token is inside the margin
122
+ * (or already expired), keep the refreshed credential in the plugin-owned
123
+ * copy, and never write the desktop app's file. A failed refresh still
124
+ * returns a not-yet-expired token so an unreachable refresh endpoint does
125
+ * not take down a working session.
126
+ */
127
+ declare class WorkBuddyCredentialStore {
128
+ private readonly refresh;
129
+ private readonly refreshMarginMs;
130
+ private readonly ownPath;
131
+ private desktopPathOverride;
132
+ private inflight;
133
+ constructor(options: WorkBuddyStoreOptions);
134
+ /**
135
+ * Configuration precedence for the desktop file: the plugin's configured
136
+ * path, then the environment variable, then the platform default.
137
+ */
138
+ private resolveDesktopPath;
139
+ /**
140
+ * Repoint the desktop file; a settings change applies on the next read.
141
+ */
142
+ setDesktopPath(path: string | undefined): void;
143
+ /** The resolved desktop auth-file path, for diagnostics. */
144
+ desktopAuthPath(): string | undefined;
145
+ /** The plugin-owned copy path, for diagnostics. */
146
+ ownAuthPath(): string;
147
+ /** Read the freshest stored credential without refreshing anything. */
148
+ current(): Promise<WorkBuddyCredential | undefined>;
149
+ /**
150
+ * The credential to send upstream: {@link current}, refreshed on demand.
151
+ * Single-flight, so parallel requests share one refresh.
152
+ */
153
+ resolve(): Promise<WorkBuddyCredential>;
154
+ /** Read-only sign-in summary; never refreshes and never throws. */
155
+ status(): Promise<WorkBuddyAuthStatus>;
156
+ /** Remove the plugin-owned copy; the desktop file is untouched. */
157
+ logout(): Promise<void>;
158
+ private needsRefresh;
159
+ private refreshNow;
160
+ private saveOwn;
161
+ private readDesktop;
162
+ private readOwn;
163
+ /** Whether the desktop file exists and is a regular file; diagnostics only. */
164
+ desktopFilePresent(): Promise<boolean>;
165
+ }
166
+ //#endregion
167
+ //#region src/catalog.d.ts
168
+ /** One model entry the adapter exposes. */
169
+ type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
170
+ /**
171
+ * Static CLI models observed on the CN endpoint (2026-08-17). The upstream
172
+ * refresh replaces this list at startup; it exists so the provider registers
173
+ * with a usable catalog even while the first fetch is in flight or offline.
174
+ */
175
+ declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
176
+ /** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
177
+ declare class WorkBuddyCatalog {
178
+ private models;
179
+ /** Current entries; the fallback list until the upstream answer lands. */
180
+ current(): readonly WorkBuddyModelInfo[];
181
+ /** Replace the list; callers invalidate their adapter snapshot after this. */
182
+ set(models: readonly WorkBuddyModelInfo[]): void;
183
+ }
184
+ //#endregion
185
+ //#region src/shim.d.ts
186
+ /** Minimal logger surface the plugin context already provides. */
187
+ interface ShimLogger {
188
+ warn(...args: unknown[]): void;
189
+ error(...args: unknown[]): void;
190
+ }
191
+ /** What the plugin needs from a running shim. */
192
+ interface WorkBuddyShim {
193
+ /** Resolves once the listener is up; rejects if listening failed. */
194
+ ready: Promise<void>;
195
+ /** The shim origin, e.g. `http://127.0.0.1:39271`; valid after ready. */
196
+ baseUrl(): string;
197
+ /**
198
+ * The per-process shared secret the plugin's own client must carry as
199
+ * `Authorization: Bearer <token>`. Lives only in memory; the adapter
200
+ * resolves this instead of the upstream access token, because the shim
201
+ * resolves the real credential itself via the store.
202
+ */
203
+ token(): string;
204
+ /** Stop serving and destroy open connections. */
205
+ close(): Promise<void>;
206
+ }
207
+ /** Constructor dependencies. */
208
+ interface WorkBuddyShimOptions {
209
+ store: WorkBuddyCredentialStore;
210
+ client: Pick<WorkBuddyUpstreamClient, 'chatStream'>;
211
+ catalog: WorkBuddyCatalog;
212
+ logger?: ShimLogger;
213
+ }
214
+ /**
215
+ * Start the loopback endpoint. Requests carry any bearer; the loopback bind
216
+ * is the boundary, and the upstream credential comes from the store alone.
217
+ */
218
+ declare function createWorkBuddyShim(options: WorkBuddyShimOptions): WorkBuddyShim;
219
+ //#endregion
220
+ //#region src/adapter.d.ts
221
+ /** Provider route this bundle owns. */
222
+ declare const WORKBUDDY_PROVIDER = "workbuddy";
223
+ /** Provider idle ceiling while one stream read is outstanding. */
224
+ declare const WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 300000;
225
+ /** Constructor dependencies. */
226
+ interface WorkBuddyAdapterOptions {
227
+ shim: WorkBuddyShim;
228
+ store: WorkBuddyCredentialStore;
229
+ catalog: WorkBuddyCatalog;
230
+ /** Resolve the durable attachment service at request time, when present. */
231
+ resolveAttachments?: () => AttachmentStore | undefined;
232
+ }
233
+ /** What {@link createWorkBuddyAdapter} hands back. */
234
+ interface WorkBuddyAdapter {
235
+ adapter: PiAiAdapter;
236
+ /** Rebuild the adapter's provider snapshot; call after a catalog update. */
237
+ invalidate: () => void;
238
+ }
239
+ /**
240
+ * Assemble the adapter. The provider's `getModels` reads the live catalog,
241
+ * and every model's `baseUrl` is re-resolved per read so the shim's
242
+ * ephemeral port applies from the first snapshot after startup.
243
+ */
244
+ declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
245
+ //#endregion
246
+ //#region src/host-heartbeat.d.ts
247
+ /**
248
+ * Host-side heartbeat: a small JSON file written under `$DSH_HOME` once the
249
+ * `workbuddy` provider is registered. The status CLI reads it to report
250
+ * whether the host bundle is alive, independent of the browser card.
251
+ *
252
+ * The browser (client) bundle cannot write files; its health is reported
253
+ * only through `console.error` on failure (see `src/client/index.tsx`).
254
+ * This asymmetry is intentional: the host is the load-bearing half, and
255
+ * a missing heartbeat unambiguously means the host never started.
256
+ *
257
+ * @module dsh-workbuddy-connect/host-heartbeat
258
+ */
259
+ /** Basename of the host heartbeat file inside the Harness home. */
260
+ declare const WORKBUDDY_HOST_HEARTBEAT_FILENAME = ".workbuddy-host-heartbeat.json";
261
+ /** Current on-disk heartbeat format; readers reject others. */
262
+ declare const HEARTBEAT_FORMAT_VERSION = 1;
263
+ /** On-disk shape of the heartbeat. */
264
+ interface WorkBuddyHostHeartbeat {
265
+ version: typeof HEARTBEAT_FORMAT_VERSION;
266
+ package: 'dsh-workbuddy-connect';
267
+ pluginVersion: string;
268
+ /** Epoch milliseconds when the host registered the provider. */
269
+ registeredAt: number;
270
+ /** Host process PID, to distinguish a stale heartbeat after a crash. */
271
+ pid: number;
272
+ }
273
+ /** Absolute path of the host heartbeat file. */
274
+ declare function workbuddyHostHeartbeatPath(): string;
275
+ /** Remove the heartbeat on plugin disposal so a stale file does not linger. */
276
+ declare function clearHostHeartbeat(): Promise<void>;
277
+ /** Read and validate the heartbeat; returns `undefined` when absent or malformed. */
278
+ declare function readHostHeartbeat(): Promise<WorkBuddyHostHeartbeat | undefined>;
279
+ /**
280
+ * Absolute start time (epoch ms) of the process holding `pid`, or `undefined`
281
+ * when it cannot be determined (no such PID, platform lacks a readable source).
282
+ *
283
+ * - macOS / Linux: `ps -o lstart=` prints a local-time "EEE MMM DD HH:MM:SS YYYY";
284
+ * `Date.parse` resolves it against the local clock, which matches how
285
+ * `registeredAt` (a `Date.now()` absolute value) is expressed.
286
+ * - Windows: WMI `CreationDate` is UTC (`YYYYMMDDHHMMSS.mmm+zzzz`); parsed with
287
+ * `Date.UTC`, again comparable to `registeredAt`.
288
+ *
289
+ * Failures return `undefined` so callers can fall back to plain PID liveness
290
+ * rather than mis-report a running host as dead.
291
+ */
292
+ declare function processStartTimeMs(pid: number): number | undefined;
293
+ /**
294
+ * Whether the heartbeat's PID is still alive *and* still the same process that
295
+ * registered it. A stale heartbeat (host crashed without clearing the file)
296
+ * is distinguished from a live host by two checks:
297
+ *
298
+ * 1. `process.kill(pid, 0)` — the PID exists (signal 0 tests existence).
299
+ * 2. The process holding that PID started at or before `registeredAt`. A host
300
+ * that registered the heartbeat must have been started before writing it,
301
+ * so `start <= registeredAt`; a recycled PID belongs to an unrelated process
302
+ * started after the host died, so `start > registeredAt` correctly reads dead.
303
+ *
304
+ * PID-only detection is not enough: after a crash the OS may hand the same PID
305
+ * to an unrelated process, and the un-cleared stale heartbeat would otherwise
306
+ * produce a false "Host running". When the process start time cannot be read
307
+ * (e.g. unsupported platform) the check degrades to plain PID liveness.
308
+ */
309
+ declare function isHeartbeatProcessAlive(heartbeat: WorkBuddyHostHeartbeat): boolean;
310
+ //#endregion
311
+ //#region src/index.d.ts
312
+ /** Stable Cordis plugin name. */
313
+ declare const name = "llm-workbuddy";
314
+ /** The model registry required before the provider can register. */
315
+ declare const inject: string[];
316
+ /** Settings namespace reserved for the future configuration card. */
317
+ declare const WORKBUDDY_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
318
+ /** Plugin configuration. */
319
+ interface Config {
320
+ /** Explicit WorkBuddy desktop auth-file path, overriding env and platform defaults. */
321
+ authFile?: string;
322
+ }
323
+ declare const Config: z<Config>;
324
+ /**
325
+ * Start the loopback endpoint, register the `workbuddy` provider, and
326
+ * refresh the model catalog from the upstream once credentials allow it.
327
+ * The static fallback catalog serves from the first moment, so an offline
328
+ * upstream never leaves the provider empty.
329
+ */
330
+ declare function apply(ctx: Context, config: Config): void;
331
+ //#endregion
332
+ export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };