@hoodcompute/sdk 0.1.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/LICENSE +21 -0
- package/README.md +332 -0
- package/dist/chunk-72VFNNOI.js +575 -0
- package/dist/chunk-72VFNNOI.js.map +1 -0
- package/dist/client-CIPi_mUM.d.cts +418 -0
- package/dist/client-CIPi_mUM.d.ts +418 -0
- package/dist/index.cjs +625 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +181 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +642 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +64 -0
- package/dist/react.d.ts +64 -0
- package/dist/react.js +74 -0
- package/dist/react.js.map +1 -0
- package/package.json +78 -0
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/react.ts
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var HoodComputeError = class extends Error {
|
|
9
|
+
constructor(message, opts = {}) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "HoodComputeError";
|
|
12
|
+
this.status = opts.status;
|
|
13
|
+
this.code = opts.code;
|
|
14
|
+
this.type = opts.type;
|
|
15
|
+
this.param = opts.param;
|
|
16
|
+
this.details = opts.details;
|
|
17
|
+
this.requestId = opts.requestId;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var AuthenticationError = class extends HoodComputeError {
|
|
21
|
+
constructor(...args) {
|
|
22
|
+
super(...args);
|
|
23
|
+
this.name = "AuthenticationError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var InsufficientCreditsError = class extends HoodComputeError {
|
|
27
|
+
constructor(...args) {
|
|
28
|
+
super(...args);
|
|
29
|
+
this.name = "InsufficientCreditsError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var InvalidRequestError = class extends HoodComputeError {
|
|
33
|
+
constructor(...args) {
|
|
34
|
+
super(...args);
|
|
35
|
+
this.name = "InvalidRequestError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var NotFoundError = class extends HoodComputeError {
|
|
39
|
+
constructor(...args) {
|
|
40
|
+
super(...args);
|
|
41
|
+
this.name = "NotFoundError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var RateLimitError = class extends HoodComputeError {
|
|
45
|
+
constructor(...args) {
|
|
46
|
+
super(...args);
|
|
47
|
+
this.name = "RateLimitError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var NoWorkersAvailableError = class extends HoodComputeError {
|
|
51
|
+
/** Suggested seconds to wait before retrying. */
|
|
52
|
+
get retryAfter() {
|
|
53
|
+
const value = this.details?.retry_after;
|
|
54
|
+
return typeof value === "number" ? value : void 0;
|
|
55
|
+
}
|
|
56
|
+
constructor(...args) {
|
|
57
|
+
super(...args);
|
|
58
|
+
this.name = "NoWorkersAvailableError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var JobTimeoutError = class extends HoodComputeError {
|
|
62
|
+
constructor(...args) {
|
|
63
|
+
super(...args);
|
|
64
|
+
this.name = "JobTimeoutError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ServerError = class extends HoodComputeError {
|
|
68
|
+
constructor(...args) {
|
|
69
|
+
super(...args);
|
|
70
|
+
this.name = "ServerError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var ConnectionError = class extends HoodComputeError {
|
|
74
|
+
constructor(message, opts = {}) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = "ConnectionError";
|
|
77
|
+
if (opts.cause !== void 0) this.cause = opts.cause;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
function errorFromResponse(status, body, requestId) {
|
|
81
|
+
const code = body?.code;
|
|
82
|
+
const message = body?.message ?? `HoodCompute API request failed with status ${status}`;
|
|
83
|
+
const opts = {
|
|
84
|
+
status,
|
|
85
|
+
code,
|
|
86
|
+
type: body?.type,
|
|
87
|
+
param: body?.param ?? null,
|
|
88
|
+
details: body?.details,
|
|
89
|
+
requestId
|
|
90
|
+
};
|
|
91
|
+
if (code === "no_workers_available") return new NoWorkersAvailableError(message, opts);
|
|
92
|
+
if (code === "job_timeout") return new JobTimeoutError(message, opts);
|
|
93
|
+
if (code === "insufficient_credits") return new InsufficientCreditsError(message, opts);
|
|
94
|
+
switch (status) {
|
|
95
|
+
case 400:
|
|
96
|
+
case 422:
|
|
97
|
+
return new InvalidRequestError(message, opts);
|
|
98
|
+
case 401:
|
|
99
|
+
case 403:
|
|
100
|
+
return new AuthenticationError(message, opts);
|
|
101
|
+
case 402:
|
|
102
|
+
return new InsufficientCreditsError(message, opts);
|
|
103
|
+
case 404:
|
|
104
|
+
return new NotFoundError(message, opts);
|
|
105
|
+
case 429:
|
|
106
|
+
return new RateLimitError(message, opts);
|
|
107
|
+
case 503:
|
|
108
|
+
return new NoWorkersAvailableError(message, opts);
|
|
109
|
+
case 504:
|
|
110
|
+
return new JobTimeoutError(message, opts);
|
|
111
|
+
default:
|
|
112
|
+
if (status >= 500) return new ServerError(message, opts);
|
|
113
|
+
return new HoodComputeError(message, opts);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function isRetryable(err) {
|
|
117
|
+
if (err instanceof ConnectionError) return true;
|
|
118
|
+
if (err instanceof HoodComputeError) {
|
|
119
|
+
if (err.status === void 0) return false;
|
|
120
|
+
return err.status >= 500 || err.status === 429;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/constants.ts
|
|
126
|
+
var DEFAULT_BASE_URL = "https://api.hoodcompute.com/v1";
|
|
127
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
128
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
129
|
+
var SDK_VERSION = "0.1.0";
|
|
130
|
+
|
|
131
|
+
// src/http.ts
|
|
132
|
+
var HttpClient = class {
|
|
133
|
+
constructor(options) {
|
|
134
|
+
this.apiKey = options.apiKey;
|
|
135
|
+
this.baseURL = options.baseURL.replace(/\/$/, "");
|
|
136
|
+
this.timeout = options.timeout;
|
|
137
|
+
this.maxRetries = options.maxRetries;
|
|
138
|
+
this.defaultHeaders = options.defaultHeaders ?? {};
|
|
139
|
+
const impl = options.fetch ?? globalThis.fetch;
|
|
140
|
+
if (!impl) {
|
|
141
|
+
throw new ConnectionError(
|
|
142
|
+
"No global fetch implementation found. Use Node 18+ or pass a `fetch` option."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
this.fetchImpl = impl;
|
|
146
|
+
}
|
|
147
|
+
/** Perform a request and return the parsed JSON body plus response headers. */
|
|
148
|
+
async request(options) {
|
|
149
|
+
const response = await this.raw(options);
|
|
150
|
+
const text = await response.text();
|
|
151
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
152
|
+
return { data, response };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Perform a request and return the raw Response. Used by the streaming path,
|
|
156
|
+
* which reads `response.body` directly. Errors are still parsed and thrown.
|
|
157
|
+
*/
|
|
158
|
+
async raw(options) {
|
|
159
|
+
const url = this.buildUrl(options.path, options.query);
|
|
160
|
+
const headers = this.buildHeaders(options);
|
|
161
|
+
const init = {
|
|
162
|
+
method: options.method ?? "GET",
|
|
163
|
+
headers
|
|
164
|
+
};
|
|
165
|
+
if (options.body !== void 0) {
|
|
166
|
+
init.body = JSON.stringify(options.body);
|
|
167
|
+
}
|
|
168
|
+
let lastError;
|
|
169
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.fetchWithTimeout(url, init, options.signal);
|
|
172
|
+
if (response.ok) return response;
|
|
173
|
+
const error = await this.toError(response);
|
|
174
|
+
if (isRetryable(error) && attempt < this.maxRetries) {
|
|
175
|
+
lastError = error;
|
|
176
|
+
await sleep(this.backoff(attempt, error));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof ConnectionError && attempt < this.maxRetries) {
|
|
182
|
+
lastError = err;
|
|
183
|
+
await sleep(this.backoff(attempt));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throw lastError instanceof Error ? lastError : new ConnectionError("Request failed after exhausting retries.");
|
|
190
|
+
}
|
|
191
|
+
async fetchWithTimeout(url, init, userSignal) {
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
194
|
+
const onAbort = () => controller.abort();
|
|
195
|
+
userSignal?.addEventListener("abort", onAbort, { once: true });
|
|
196
|
+
try {
|
|
197
|
+
return await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (userSignal?.aborted) {
|
|
200
|
+
throw new ConnectionError("Request aborted by caller.", { cause: err });
|
|
201
|
+
}
|
|
202
|
+
if (controller.signal.aborted) {
|
|
203
|
+
throw new ConnectionError(`Request timed out after ${this.timeout}ms.`, { cause: err });
|
|
204
|
+
}
|
|
205
|
+
throw new ConnectionError("Failed to reach the HoodCompute API.", { cause: err });
|
|
206
|
+
} finally {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
userSignal?.removeEventListener("abort", onAbort);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async toError(response) {
|
|
212
|
+
let body;
|
|
213
|
+
try {
|
|
214
|
+
const parsed = await response.json();
|
|
215
|
+
body = "error" in parsed && parsed.error ? parsed.error : parsed;
|
|
216
|
+
} catch {
|
|
217
|
+
body = void 0;
|
|
218
|
+
}
|
|
219
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
220
|
+
return errorFromResponse(response.status, body, requestId);
|
|
221
|
+
}
|
|
222
|
+
buildUrl(path, query) {
|
|
223
|
+
const url = new URL(`${this.baseURL}${path}`);
|
|
224
|
+
if (query) {
|
|
225
|
+
for (const [key, value] of Object.entries(query)) {
|
|
226
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return url.toString();
|
|
230
|
+
}
|
|
231
|
+
buildHeaders(options) {
|
|
232
|
+
const headers = {
|
|
233
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
234
|
+
Accept: options.stream ? "text/event-stream" : "application/json",
|
|
235
|
+
"User-Agent": `hoodcompute-sdk-js/${SDK_VERSION}`,
|
|
236
|
+
...this.defaultHeaders
|
|
237
|
+
};
|
|
238
|
+
if (options.body !== void 0) {
|
|
239
|
+
headers["Content-Type"] = "application/json";
|
|
240
|
+
}
|
|
241
|
+
return headers;
|
|
242
|
+
}
|
|
243
|
+
backoff(attempt, error) {
|
|
244
|
+
const retryAfter = error && typeof error === "object" && "details" in error && typeof error.details?.retry_after === "number" ? error.details.retry_after * 1e3 : void 0;
|
|
245
|
+
if (retryAfter !== void 0) return retryAfter;
|
|
246
|
+
return Math.min(1e3 * 2 ** attempt, 8e3);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function sleep(ms) {
|
|
250
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/streaming.ts
|
|
254
|
+
var ChatCompletionStream = class {
|
|
255
|
+
constructor(response) {
|
|
256
|
+
this.response = response;
|
|
257
|
+
this.jobId = response.headers.get("x-hoodcompute-job-id");
|
|
258
|
+
this.receipt = this.readReceipt();
|
|
259
|
+
}
|
|
260
|
+
async *[Symbol.asyncIterator]() {
|
|
261
|
+
const body = this.response.body;
|
|
262
|
+
if (!body) {
|
|
263
|
+
throw new ConnectionError("The streaming response contained no body.");
|
|
264
|
+
}
|
|
265
|
+
const decoder = new TextDecoder();
|
|
266
|
+
const reader = body.getReader();
|
|
267
|
+
let buffer = "";
|
|
268
|
+
try {
|
|
269
|
+
while (true) {
|
|
270
|
+
const { done, value } = await reader.read();
|
|
271
|
+
if (done) break;
|
|
272
|
+
buffer += decoder.decode(value, { stream: true });
|
|
273
|
+
let boundary = buffer.indexOf("\n\n");
|
|
274
|
+
while (boundary !== -1) {
|
|
275
|
+
const rawEvent = buffer.slice(0, boundary);
|
|
276
|
+
buffer = buffer.slice(boundary + 2);
|
|
277
|
+
const chunk = parseEvent(rawEvent);
|
|
278
|
+
if (chunk === DONE) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (chunk) {
|
|
282
|
+
yield chunk;
|
|
283
|
+
}
|
|
284
|
+
boundary = buffer.indexOf("\n\n");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const trailing = parseEvent(buffer);
|
|
288
|
+
if (trailing && trailing !== DONE) {
|
|
289
|
+
yield trailing;
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
reader.releaseLock();
|
|
293
|
+
this.receipt = this.readReceipt();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/** Convenience: collect the full concatenated text of the stream. */
|
|
297
|
+
async text() {
|
|
298
|
+
let out = "";
|
|
299
|
+
for await (const chunk of this) {
|
|
300
|
+
out += chunk.choices[0]?.delta?.content ?? "";
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
readReceipt() {
|
|
305
|
+
const h = this.response.headers;
|
|
306
|
+
const creditsRemaining = h.get("x-hoodcompute-credits-remaining");
|
|
307
|
+
return {
|
|
308
|
+
jobId: h.get("x-hoodcompute-job-id"),
|
|
309
|
+
settlementTx: h.get("x-hoodcompute-settlement-tx"),
|
|
310
|
+
escrowTx: h.get("x-hoodcompute-tx-hash"),
|
|
311
|
+
workerAddress: h.get("x-hoodcompute-worker"),
|
|
312
|
+
creditsRemaining: creditsRemaining !== null ? Number(creditsRemaining) : null
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
var DONE = /* @__PURE__ */ Symbol("done");
|
|
317
|
+
function parseEvent(raw) {
|
|
318
|
+
const dataLines = [];
|
|
319
|
+
for (const line of raw.split("\n")) {
|
|
320
|
+
const trimmed = line.trimEnd();
|
|
321
|
+
if (trimmed.startsWith(":") || trimmed === "") continue;
|
|
322
|
+
if (trimmed.startsWith("data:")) {
|
|
323
|
+
dataLines.push(trimmed.slice(5).trimStart());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (dataLines.length === 0) return null;
|
|
327
|
+
const data = dataLines.join("\n");
|
|
328
|
+
if (data === "[DONE]") return DONE;
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(data);
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/resources/chat.ts
|
|
337
|
+
var Completions = class {
|
|
338
|
+
constructor(http) {
|
|
339
|
+
this.http = http;
|
|
340
|
+
}
|
|
341
|
+
async create(params, options = {}) {
|
|
342
|
+
if (params.stream) {
|
|
343
|
+
const response2 = await this.http.raw({
|
|
344
|
+
method: "POST",
|
|
345
|
+
path: "/chat/completions",
|
|
346
|
+
body: params,
|
|
347
|
+
stream: true,
|
|
348
|
+
signal: options.signal
|
|
349
|
+
});
|
|
350
|
+
return new ChatCompletionStream(response2);
|
|
351
|
+
}
|
|
352
|
+
const { data, response } = await this.http.request({
|
|
353
|
+
method: "POST",
|
|
354
|
+
path: "/chat/completions",
|
|
355
|
+
body: params,
|
|
356
|
+
signal: options.signal
|
|
357
|
+
});
|
|
358
|
+
const creditsRemaining = response.headers.get("x-hoodcompute-credits-remaining");
|
|
359
|
+
return {
|
|
360
|
+
...data,
|
|
361
|
+
jobId: response.headers.get("x-hoodcompute-job-id") ?? data.id,
|
|
362
|
+
settlementTx: response.headers.get("x-hoodcompute-settlement-tx"),
|
|
363
|
+
creditsCharged: null,
|
|
364
|
+
creditsRemaining: creditsRemaining !== null ? Number(creditsRemaining) : null
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var Chat = class {
|
|
369
|
+
constructor(http) {
|
|
370
|
+
this.completions = new Completions(http);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/resources/models.ts
|
|
375
|
+
function normalize(raw) {
|
|
376
|
+
return {
|
|
377
|
+
id: raw.id,
|
|
378
|
+
object: raw.object,
|
|
379
|
+
created: raw.created,
|
|
380
|
+
ownedBy: raw.owned_by,
|
|
381
|
+
hoodcompute: {
|
|
382
|
+
tier: raw.hoodcompute.tier,
|
|
383
|
+
creditsPerRequest: raw.hoodcompute.credits_per_request,
|
|
384
|
+
creditsPer1kTokens: raw.hoodcompute.credits_per_1k_tokens,
|
|
385
|
+
activeWorkers: raw.hoodcompute.active_workers,
|
|
386
|
+
medianLatencyMs: raw.hoodcompute.median_latency_ms,
|
|
387
|
+
parameters: raw.hoodcompute.parameters,
|
|
388
|
+
contextWindow: raw.hoodcompute.context_window
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
var Models = class {
|
|
393
|
+
constructor(http) {
|
|
394
|
+
this.http = http;
|
|
395
|
+
}
|
|
396
|
+
/** List every model currently available on the network. */
|
|
397
|
+
async list(options = {}) {
|
|
398
|
+
const { data } = await this.http.request({
|
|
399
|
+
path: "/models",
|
|
400
|
+
signal: options.signal
|
|
401
|
+
});
|
|
402
|
+
return { object: "list", data: data.data.map(normalize) };
|
|
403
|
+
}
|
|
404
|
+
/** Retrieve a single model by ID, including its live worker count. */
|
|
405
|
+
async retrieve(modelId, options = {}) {
|
|
406
|
+
const { data } = await this.http.request({
|
|
407
|
+
path: `/models/${encodeURIComponent(modelId)}`,
|
|
408
|
+
signal: options.signal
|
|
409
|
+
});
|
|
410
|
+
return normalize(data);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/resources/jobs.ts
|
|
415
|
+
function normalize2(raw) {
|
|
416
|
+
return {
|
|
417
|
+
id: raw.id,
|
|
418
|
+
object: raw.object,
|
|
419
|
+
status: raw.status,
|
|
420
|
+
model: raw.model,
|
|
421
|
+
tier: raw.tier,
|
|
422
|
+
creditsCharged: raw.credits_charged,
|
|
423
|
+
usdgValue: raw.usdg_value,
|
|
424
|
+
workerAddress: raw.worker_address,
|
|
425
|
+
createdAt: raw.created_at,
|
|
426
|
+
completedAt: raw.completed_at,
|
|
427
|
+
onChain: raw.on_chain ? {
|
|
428
|
+
escrowTx: raw.on_chain.escrow_tx,
|
|
429
|
+
settlementTx: raw.on_chain.settlement_tx,
|
|
430
|
+
escrowAddress: raw.on_chain.escrow_address,
|
|
431
|
+
blockNumber: raw.on_chain.block_number,
|
|
432
|
+
proofHash: raw.on_chain.proof_hash
|
|
433
|
+
} : null,
|
|
434
|
+
usage: raw.usage
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
var Jobs = class {
|
|
438
|
+
constructor(http) {
|
|
439
|
+
this.http = http;
|
|
440
|
+
}
|
|
441
|
+
/** Retrieve a job by ID, including its on-chain settlement detail. */
|
|
442
|
+
async get(jobId, options = {}) {
|
|
443
|
+
const { data } = await this.http.request({
|
|
444
|
+
path: `/jobs/${encodeURIComponent(jobId)}`,
|
|
445
|
+
signal: options.signal
|
|
446
|
+
});
|
|
447
|
+
return normalize2(data);
|
|
448
|
+
}
|
|
449
|
+
/** List jobs for the authenticated key, most recent first. */
|
|
450
|
+
async list(params = {}, options = {}) {
|
|
451
|
+
const { data } = await this.http.request({
|
|
452
|
+
path: "/jobs",
|
|
453
|
+
query: {
|
|
454
|
+
limit: params.limit,
|
|
455
|
+
before: params.before,
|
|
456
|
+
after: params.after,
|
|
457
|
+
status: params.status,
|
|
458
|
+
model: params.model
|
|
459
|
+
},
|
|
460
|
+
signal: options.signal
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
object: "list",
|
|
464
|
+
data: data.data.map(normalize2),
|
|
465
|
+
hasMore: data.has_more,
|
|
466
|
+
nextCursor: data.next_cursor
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Fetch the on-chain receipt for a settled job. Throws if the job exists but
|
|
471
|
+
* has not settled yet.
|
|
472
|
+
*/
|
|
473
|
+
async getReceipt(jobId, options = {}) {
|
|
474
|
+
const job = await this.get(jobId, options);
|
|
475
|
+
if (!job.onChain) {
|
|
476
|
+
throw new NotFoundError(
|
|
477
|
+
`Job ${jobId} has status "${job.status}" and no on-chain receipt yet.`,
|
|
478
|
+
{ code: "receipt_not_ready" }
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
jobId: job.id,
|
|
483
|
+
model: job.model,
|
|
484
|
+
tier: job.tier,
|
|
485
|
+
creditsCharged: job.creditsCharged,
|
|
486
|
+
usdgValue: job.usdgValue,
|
|
487
|
+
workerAddress: job.workerAddress,
|
|
488
|
+
escrowTx: job.onChain.escrowTx,
|
|
489
|
+
settlementTx: job.onChain.settlementTx,
|
|
490
|
+
blockNumber: job.onChain.blockNumber,
|
|
491
|
+
proofHash: job.onChain.proofHash
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Open a dispute on a completed job. Must be called within 60 seconds of
|
|
496
|
+
* receiving the final output token.
|
|
497
|
+
*
|
|
498
|
+
* @param receivedOutputHash SHA-256 of the full response text you received,
|
|
499
|
+
* formatted as `sha256:<hex>`.
|
|
500
|
+
*/
|
|
501
|
+
async dispute(jobId, receivedOutputHash, options = {}) {
|
|
502
|
+
const { data } = await this.http.request({
|
|
503
|
+
method: "POST",
|
|
504
|
+
path: `/jobs/${encodeURIComponent(jobId)}/dispute`,
|
|
505
|
+
body: { received_output_hash: receivedOutputHash },
|
|
506
|
+
signal: options.signal
|
|
507
|
+
});
|
|
508
|
+
return {
|
|
509
|
+
jobId: data.job_id,
|
|
510
|
+
disputeOpened: data.dispute_opened,
|
|
511
|
+
yourHash: data.your_hash,
|
|
512
|
+
workerHash: data.worker_hash,
|
|
513
|
+
disputeTx: data.dispute_tx ?? null,
|
|
514
|
+
arbitrationWindowHours: data.arbitration_window_hours ?? null
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/resources/account.ts
|
|
520
|
+
var AccountResource = class {
|
|
521
|
+
constructor(http) {
|
|
522
|
+
this.http = http;
|
|
523
|
+
}
|
|
524
|
+
/** Retrieve the current account, including the live credit balance. */
|
|
525
|
+
async get(options = {}) {
|
|
526
|
+
const { data } = await this.http.request({
|
|
527
|
+
path: "/account",
|
|
528
|
+
signal: options.signal
|
|
529
|
+
});
|
|
530
|
+
return {
|
|
531
|
+
wallet: data.wallet,
|
|
532
|
+
creditsRemaining: data.credits_remaining,
|
|
533
|
+
usdgValue: data.usdg_value,
|
|
534
|
+
lastTopupAt: data.last_topup_at ?? null,
|
|
535
|
+
apiKeyCreatedAt: data.api_key_created_at ?? null
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/client.ts
|
|
541
|
+
var HoodComputeClient = class {
|
|
542
|
+
constructor(options = {}) {
|
|
543
|
+
const apiKey = options.apiKey ?? readEnv("HOODCOMPUTE_API_KEY");
|
|
544
|
+
if (!apiKey) {
|
|
545
|
+
throw new HoodComputeError(
|
|
546
|
+
"Missing API key. Pass `apiKey` to the client or set the HOODCOMPUTE_API_KEY environment variable.",
|
|
547
|
+
{ code: "missing_api_key" }
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
const http = new HttpClient({
|
|
551
|
+
apiKey,
|
|
552
|
+
baseURL: options.baseURL ?? DEFAULT_BASE_URL,
|
|
553
|
+
timeout: options.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
554
|
+
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
555
|
+
defaultHeaders: options.defaultHeaders,
|
|
556
|
+
fetch: options.fetch
|
|
557
|
+
});
|
|
558
|
+
this.chat = new Chat(http);
|
|
559
|
+
this.models = new Models(http);
|
|
560
|
+
this.jobs = new Jobs(http);
|
|
561
|
+
this.account = new AccountResource(http);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function readEnv(name) {
|
|
565
|
+
if (typeof process !== "undefined" && process.env) {
|
|
566
|
+
return process.env[name];
|
|
567
|
+
}
|
|
568
|
+
return void 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/react.ts
|
|
572
|
+
function useHoodComputeChat(options) {
|
|
573
|
+
const { model, apiKey, baseURL, client: providedClient, systemPrompt, onComplete, onError } = options;
|
|
574
|
+
const [messages, setMessages] = react.useState([]);
|
|
575
|
+
const [status, setStatus] = react.useState("idle");
|
|
576
|
+
const [balance, setBalance] = react.useState(null);
|
|
577
|
+
const [lastReceipt, setLastReceipt] = react.useState(null);
|
|
578
|
+
const [error, setError] = react.useState(null);
|
|
579
|
+
const client = react.useMemo(
|
|
580
|
+
() => providedClient ?? new HoodComputeClient({ apiKey, baseURL }),
|
|
581
|
+
[providedClient, apiKey, baseURL]
|
|
582
|
+
);
|
|
583
|
+
const callbacks = react.useRef({ onComplete, onError });
|
|
584
|
+
callbacks.current = { onComplete, onError };
|
|
585
|
+
const send = react.useCallback(
|
|
586
|
+
async (message) => {
|
|
587
|
+
setError(null);
|
|
588
|
+
setStatus("streaming");
|
|
589
|
+
const userMessage = { role: "user", content: message };
|
|
590
|
+
const history = [...messages, userMessage];
|
|
591
|
+
setMessages((prev) => [...prev, userMessage, { role: "assistant", content: "" }]);
|
|
592
|
+
const outbound = systemPrompt ? [{ role: "system", content: systemPrompt }, ...history] : history;
|
|
593
|
+
try {
|
|
594
|
+
const stream = await client.chat.completions.create({
|
|
595
|
+
model,
|
|
596
|
+
messages: outbound,
|
|
597
|
+
stream: true
|
|
598
|
+
});
|
|
599
|
+
for await (const chunk of stream) {
|
|
600
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
601
|
+
if (!delta) continue;
|
|
602
|
+
setMessages((prev) => {
|
|
603
|
+
const next = [...prev];
|
|
604
|
+
const last = next[next.length - 1];
|
|
605
|
+
if (last && last.role === "assistant") {
|
|
606
|
+
next[next.length - 1] = { ...last, content: last.content + delta };
|
|
607
|
+
}
|
|
608
|
+
return next;
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
setStatus("settling");
|
|
612
|
+
const receipt = stream.receipt;
|
|
613
|
+
setLastReceipt(receipt);
|
|
614
|
+
if (receipt.creditsRemaining !== null) {
|
|
615
|
+
setBalance({
|
|
616
|
+
creditsRemaining: receipt.creditsRemaining,
|
|
617
|
+
usdgValue: Number((receipt.creditsRemaining * 0.01).toFixed(2))
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
callbacks.current.onComplete?.(receipt);
|
|
621
|
+
setStatus("idle");
|
|
622
|
+
} catch (err) {
|
|
623
|
+
const normalized = err instanceof Error ? err : new Error(String(err));
|
|
624
|
+
setError(normalized);
|
|
625
|
+
setStatus("error");
|
|
626
|
+
callbacks.current.onError?.(normalized);
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
[client, model, messages, systemPrompt]
|
|
630
|
+
);
|
|
631
|
+
const reset = react.useCallback(() => {
|
|
632
|
+
setMessages([]);
|
|
633
|
+
setStatus("idle");
|
|
634
|
+
setError(null);
|
|
635
|
+
setLastReceipt(null);
|
|
636
|
+
}, []);
|
|
637
|
+
return { send, messages, status, balance, lastReceipt, error, reset };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
exports.useHoodComputeChat = useHoodComputeChat;
|
|
641
|
+
//# sourceMappingURL=react.cjs.map
|
|
642
|
+
//# sourceMappingURL=react.cjs.map
|