@denpex/sdk 0.8.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/README.md +49 -0
- package/dist/client.d.ts +99 -0
- package/dist/client.js +98 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @denpex/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript/JavaScript client for the [Denpex](https://denpex.com) API — diagnose GPU training
|
|
4
|
+
failures from logs, report fix outcomes, and check your plan quota. Zero runtime dependencies,
|
|
5
|
+
Node ≥ 18.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @denpex/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { DenpexClient } from "@denpex/sdk";
|
|
17
|
+
|
|
18
|
+
const denpex = new DenpexClient({ apiKey: process.env.DENPEX_API_KEY! });
|
|
19
|
+
|
|
20
|
+
// Diagnose a training log
|
|
21
|
+
const result = await denpex.diagnose(logText, { jobName: "llama3-405b-pretrain" });
|
|
22
|
+
console.log(result.meta?.failureType, result.meta?.confidence);
|
|
23
|
+
console.log(result.fix?.summary);
|
|
24
|
+
console.log(result.fix?.action);
|
|
25
|
+
|
|
26
|
+
// Close the accuracy flywheel by reporting whether the fix worked
|
|
27
|
+
await denpex.feedback(result.diagnosisId, "worked");
|
|
28
|
+
|
|
29
|
+
// Check remaining headroom for the key's plan
|
|
30
|
+
const quota = await denpex.quota();
|
|
31
|
+
console.log(quota.quota);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Create an API key at <https://denpex.com/dashboard>. It is sent as the `X-Denpex-Key` header.
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
| Method | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `new DenpexClient({ apiKey, baseUrl?, timeoutMs? })` | Construct a client. `baseUrl` defaults to `https://api.denpex.com`; the default 320-second timeout covers the server's 290-second deep path. |
|
|
41
|
+
| `diagnose(logs, opts?)` | `POST /api/diagnose` — returns the diagnosis (failure type, confidence, root cause, fix, evidence). |
|
|
42
|
+
| `feedback(diagnosisId, outcome, note?)` | `POST /api/diagnose/feedback` — `outcome` is `"worked"` or `"did_not_work"`. |
|
|
43
|
+
| `quota()` | `GET /api/diagnose/quota` — remaining diagnoses for the key's plan. |
|
|
44
|
+
|
|
45
|
+
Errors throw `DenpexError` with the HTTP status and server message.
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
Proprietary. © Denpex. Use of this SDK is governed by your Denpex subscription agreement.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface DenpexClientOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
/** Defaults to the production control plane. */
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Override fetch (tests, custom agents). Defaults to globalThis.fetch. */
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
/** Per-request timeout in ms (AbortController). Default 320s — server deep-path ceiling is 290s. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface DiagnoseOptions {
|
|
11
|
+
jobName?: string;
|
|
12
|
+
runtimeHours?: number;
|
|
13
|
+
gpuRate?: number;
|
|
14
|
+
topologyMap?: Record<string, string[]>;
|
|
15
|
+
/** Profiler traces, job metadata, and other structured telemetry used for grounding. */
|
|
16
|
+
telemetry?: Record<string, unknown>;
|
|
17
|
+
/** Relevant source/config fragments used to test software-stack hypotheses. */
|
|
18
|
+
codeContext?: Record<string, unknown>;
|
|
19
|
+
/** Client-side signature matches (e.g. from denpex_lite) so Tier 0 starts from evidence. */
|
|
20
|
+
signatures?: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface DiagnoseMeta {
|
|
23
|
+
failureType?: string;
|
|
24
|
+
confidence?: number;
|
|
25
|
+
source?: string;
|
|
26
|
+
tier?: number;
|
|
27
|
+
/** Engine build stamp — compare across surfaces to detect a stale deploy. */
|
|
28
|
+
engineVersion?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The known response fields. The engine attaches additional sections as it
|
|
32
|
+
* grows (forensics, advisories, execution plans) — they pass through untyped
|
|
33
|
+
* rather than being silently dropped, so SDK users always receive everything
|
|
34
|
+
* the API returned.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Which rank failed FIRST, on which node, and the line that proves it.
|
|
38
|
+
*
|
|
39
|
+
* On a 512-rank job every rank reports a timeout and exactly one started it, so
|
|
40
|
+
* this is usually the field a caller actually wants. `node` is parsed directly
|
|
41
|
+
* off the matched line; `rank` is inferred from the nearest `[rank N]` within a
|
|
42
|
+
* few lines, so treat the node as the stronger signal of the two.
|
|
43
|
+
*
|
|
44
|
+
* `analyzedLines` < `totalLines` means the paste exceeded the window the engine
|
|
45
|
+
* reads — the verdict covers only what was analysed.
|
|
46
|
+
*/
|
|
47
|
+
export interface DiagnoseInitiator {
|
|
48
|
+
rank?: number | null;
|
|
49
|
+
node?: string | null;
|
|
50
|
+
line?: string | null;
|
|
51
|
+
lineNumber?: number | null;
|
|
52
|
+
totalLines?: number | null;
|
|
53
|
+
analyzedLines?: number | null;
|
|
54
|
+
}
|
|
55
|
+
export interface DiagnoseResponse {
|
|
56
|
+
fix?: {
|
|
57
|
+
summary?: string;
|
|
58
|
+
action?: string;
|
|
59
|
+
evidence?: unknown[];
|
|
60
|
+
[k: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
meta?: DiagnoseMeta;
|
|
63
|
+
initiator?: DiagnoseInitiator | null;
|
|
64
|
+
engineVersion?: string;
|
|
65
|
+
remaining?: number;
|
|
66
|
+
forensicReport?: unknown;
|
|
67
|
+
advisory?: unknown[];
|
|
68
|
+
researchRefs?: unknown[];
|
|
69
|
+
communityRefs?: unknown[];
|
|
70
|
+
executionPlan?: unknown;
|
|
71
|
+
[k: string]: unknown;
|
|
72
|
+
}
|
|
73
|
+
export interface QuotaResponse {
|
|
74
|
+
[k: string]: unknown;
|
|
75
|
+
}
|
|
76
|
+
export type FeedbackOutcome = 'worked' | 'did_not_work';
|
|
77
|
+
export declare class DenpexError extends Error {
|
|
78
|
+
readonly status: number;
|
|
79
|
+
/** Machine-readable code when the API provides one (e.g. AUTH_REQUIRED, GPU_LIMIT_EXCEEDED). */
|
|
80
|
+
readonly code?: string | undefined;
|
|
81
|
+
constructor(message: string, status: number,
|
|
82
|
+
/** Machine-readable code when the API provides one (e.g. AUTH_REQUIRED, GPU_LIMIT_EXCEEDED). */
|
|
83
|
+
code?: string | undefined);
|
|
84
|
+
}
|
|
85
|
+
export declare class DenpexClient {
|
|
86
|
+
private readonly apiKey;
|
|
87
|
+
private readonly baseUrl;
|
|
88
|
+
private readonly fetchImpl;
|
|
89
|
+
private readonly timeoutMs;
|
|
90
|
+
constructor(options: DenpexClientOptions);
|
|
91
|
+
/** Diagnose a training failure from raw logs (20 chars – 256 KB). */
|
|
92
|
+
diagnose(logs: string, opts?: DiagnoseOptions): Promise<DiagnoseResponse>;
|
|
93
|
+
/** Report whether a diagnosis worked — this feedback directly improves future priors. */
|
|
94
|
+
feedback(diagnosisId: string, outcome: FeedbackOutcome, note?: string): Promise<Record<string, unknown>>;
|
|
95
|
+
/** Remaining diagnosis headroom for this key's plan. */
|
|
96
|
+
quota(): Promise<QuotaResponse>;
|
|
97
|
+
private request;
|
|
98
|
+
}
|
|
99
|
+
export { DenpexClient as default };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// @denpex/sdk — TypeScript client for the Denpex public API.
|
|
3
|
+
//
|
|
4
|
+
// Rewritten 2026-07-20: the previous file was a stub for /v1/{tenant}/incidents
|
|
5
|
+
// routes that do not exist in the api-worker. This client targets the REAL
|
|
6
|
+
// production surface (matching worker/api-worker-optimized.ts):
|
|
7
|
+
//
|
|
8
|
+
// POST /api/diagnose { logs, jobName?, ... } -> diagnosis
|
|
9
|
+
// POST /api/diagnose/feedback { diagnosisId, outcome } -> closes the accuracy flywheel
|
|
10
|
+
// GET /api/diagnose/quota remaining headroom for the key's plan
|
|
11
|
+
//
|
|
12
|
+
// Auth: an API key from https://denpex.com/dashboard, sent as `X-Denpex-Key`
|
|
13
|
+
// (`dpx_`/`dpk_`-prefixed keys may also be sent as a Bearer token).
|
|
14
|
+
//
|
|
15
|
+
// Zero dependencies. Requires global fetch (Node >= 18 or any browser/worker).
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.default = exports.DenpexClient = exports.DenpexError = void 0;
|
|
18
|
+
class DenpexError extends Error {
|
|
19
|
+
constructor(message, status,
|
|
20
|
+
/** Machine-readable code when the API provides one (e.g. AUTH_REQUIRED, GPU_LIMIT_EXCEEDED). */
|
|
21
|
+
code) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = 'DenpexError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.DenpexError = DenpexError;
|
|
29
|
+
const MIN_LOG_CHARS = 20;
|
|
30
|
+
const MAX_LOG_CHARS = 256000;
|
|
31
|
+
class DenpexClient {
|
|
32
|
+
constructor(options) {
|
|
33
|
+
if (!options?.apiKey)
|
|
34
|
+
throw new Error('DenpexClient: apiKey is required (create one at https://denpex.com/dashboard)');
|
|
35
|
+
this.apiKey = options.apiKey;
|
|
36
|
+
this.baseUrl = (options.baseUrl ?? 'https://api.denpex.com').replace(/\/+$/, '');
|
|
37
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
38
|
+
this.timeoutMs = options.timeoutMs ?? 320000;
|
|
39
|
+
if (!this.fetchImpl)
|
|
40
|
+
throw new Error('DenpexClient: global fetch not found — Node >= 18 required, or pass fetchImpl');
|
|
41
|
+
}
|
|
42
|
+
/** Diagnose a training failure from raw logs (20 chars – 256 KB). */
|
|
43
|
+
async diagnose(logs, opts = {}) {
|
|
44
|
+
if (typeof logs !== 'string' || logs.length < MIN_LOG_CHARS) {
|
|
45
|
+
throw new DenpexError(`logs must be at least ${MIN_LOG_CHARS} characters`, 400);
|
|
46
|
+
}
|
|
47
|
+
if (logs.length > MAX_LOG_CHARS) {
|
|
48
|
+
throw new DenpexError(`logs too large (max ${MAX_LOG_CHARS} chars) — send the failing excerpt`, 400);
|
|
49
|
+
}
|
|
50
|
+
return this.request('POST', '/api/diagnose', { logs, ...opts });
|
|
51
|
+
}
|
|
52
|
+
/** Report whether a diagnosis worked — this feedback directly improves future priors. */
|
|
53
|
+
async feedback(diagnosisId, outcome, note) {
|
|
54
|
+
if (!diagnosisId)
|
|
55
|
+
throw new DenpexError('diagnosisId required', 400);
|
|
56
|
+
return this.request('POST', '/api/diagnose/feedback', { diagnosisId, outcome, ...(note ? { note } : {}) });
|
|
57
|
+
}
|
|
58
|
+
/** Remaining diagnosis headroom for this key's plan. */
|
|
59
|
+
async quota() {
|
|
60
|
+
return this.request('GET', '/api/diagnose/quota');
|
|
61
|
+
}
|
|
62
|
+
async request(method, path, body) {
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
65
|
+
try {
|
|
66
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
67
|
+
method,
|
|
68
|
+
headers: {
|
|
69
|
+
'X-Denpex-Key': this.apiKey,
|
|
70
|
+
Accept: 'application/json',
|
|
71
|
+
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
72
|
+
},
|
|
73
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
});
|
|
76
|
+
let json = {};
|
|
77
|
+
try {
|
|
78
|
+
json = (await res.json());
|
|
79
|
+
}
|
|
80
|
+
catch { /* non-JSON error body */ }
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const message = typeof json.error === 'string' ? json.error : `Denpex API error (status ${res.status})`;
|
|
83
|
+
throw new DenpexError(message, res.status, typeof json.code === 'string' ? json.code : undefined);
|
|
84
|
+
}
|
|
85
|
+
return json;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
if (e.name === 'AbortError')
|
|
89
|
+
throw new DenpexError(`request timed out after ${this.timeoutMs}ms`, 408);
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.DenpexClient = DenpexClient;
|
|
98
|
+
exports.default = DenpexClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@denpex/sdk",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "TypeScript client for the Denpex API — diagnose GPU training failures from logs (POST /api/diagnose), report fix outcomes, check quota. Zero dependencies, Node >= 18.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"denpex",
|
|
7
|
+
"gpu",
|
|
8
|
+
"ml-training",
|
|
9
|
+
"distributed-training",
|
|
10
|
+
"diagnosis",
|
|
11
|
+
"nccl",
|
|
12
|
+
"cuda",
|
|
13
|
+
"observability",
|
|
14
|
+
"sdk"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://denpex.com",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/MisaMisaAI/Denpex.git",
|
|
20
|
+
"directory": "denpex-sdk-ts"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://denpex.com/contact"
|
|
24
|
+
},
|
|
25
|
+
"license": "Proprietary",
|
|
26
|
+
"main": "dist/client.js",
|
|
27
|
+
"types": "dist/client.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"prepublishOnly": "npm run build",
|
|
41
|
+
"test": "tsx --test tests/client.test.ts"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.0.0",
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"tsx": "^4.23.1"
|
|
47
|
+
}
|
|
48
|
+
}
|