@astralform/js 4.0.0 → 4.2.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 +6 -0
- package/dist/index.cjs +54 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +54 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,9 +61,15 @@ const session = new ChatSession({
|
|
|
61
61
|
userId: "user-123", // Required — identifies the end user
|
|
62
62
|
baseURL: "http://localhost:8000", // Optional — defaults to https://api.astralform.ai
|
|
63
63
|
fetch: customFetch, // Optional — custom fetch implementation
|
|
64
|
+
timeoutMs: 30_000, // Optional — REST request deadline, defaults to 30s
|
|
64
65
|
});
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
`timeoutMs` bounds each REST request end to end — connect, headers, and the
|
|
69
|
+
body read — rejecting with `ConnectionError` when it expires. File uploads and
|
|
70
|
+
the SSE stream are exempt: both are long-running by design, and the stream
|
|
71
|
+
carries its own `AbortSignal`.
|
|
72
|
+
|
|
67
73
|
## Events
|
|
68
74
|
|
|
69
75
|
Subscribe to events with `.on()`, which returns an unsubscribe function. The SDK forwards a typed `ChatEvent` for every wire event — consumers build their own block / message state from the stream.
|
package/dist/index.cjs
CHANGED
|
@@ -298,6 +298,7 @@ async function* streamJobSSE(options) {
|
|
|
298
298
|
|
|
299
299
|
// src/client.ts
|
|
300
300
|
var DEFAULT_BASE_URL = "https://api.astralform.ai";
|
|
301
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
301
302
|
function validateBaseURL(url) {
|
|
302
303
|
const cleaned = url.replace(/\/+$/, "");
|
|
303
304
|
try {
|
|
@@ -348,6 +349,7 @@ var AstralformClient = class {
|
|
|
348
349
|
}
|
|
349
350
|
this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);
|
|
350
351
|
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
352
|
+
this.timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
351
353
|
}
|
|
352
354
|
/**
|
|
353
355
|
* Replace the current OIDC access token without reconstructing the client.
|
|
@@ -438,11 +440,48 @@ var AstralformClient = class {
|
|
|
438
440
|
"Content-Type": "application/json"
|
|
439
441
|
};
|
|
440
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Run one REST exchange under a single deadline covering connect, headers,
|
|
445
|
+
* AND the body read. The body read is the part that matters: `json()` used
|
|
446
|
+
* to sit outside every guard, so a response whose headers arrived but whose
|
|
447
|
+
* body stalled hung forever — silently stranding callers that await it
|
|
448
|
+
* (a stalled `getMessages` used to leave `StreamManager.restore()` parked
|
|
449
|
+
* before it ever fetched the events it renders from).
|
|
450
|
+
*
|
|
451
|
+
* The controller is created per request and is deliberately NOT the
|
|
452
|
+
* session's — that one means "the user cancelled this turn" and is null
|
|
453
|
+
* outside a live turn. Aborting frees the socket; the race guarantees a
|
|
454
|
+
* rejection even when an injected `fetch` ignores the signal.
|
|
455
|
+
*/
|
|
456
|
+
async withDeadline(run) {
|
|
457
|
+
const controller = new AbortController();
|
|
458
|
+
const timedOut = () => new ConnectionError(`Request timed out after ${this.timeoutMs}ms`);
|
|
459
|
+
let timer;
|
|
460
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
461
|
+
timer = setTimeout(() => {
|
|
462
|
+
controller.abort();
|
|
463
|
+
reject(timedOut());
|
|
464
|
+
}, this.timeoutMs);
|
|
465
|
+
});
|
|
466
|
+
try {
|
|
467
|
+
return await Promise.race([run(controller.signal), deadline]);
|
|
468
|
+
} catch (err) {
|
|
469
|
+
if (controller.signal.aborted) throw timedOut();
|
|
470
|
+
throw err;
|
|
471
|
+
} finally {
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
441
475
|
async request(method, path, body) {
|
|
476
|
+
return this.withDeadline((signal) => this.send(method, path, body, signal));
|
|
477
|
+
}
|
|
478
|
+
/** Fetch + status handling. Always called inside `withDeadline`. */
|
|
479
|
+
async send(method, path, body, signal) {
|
|
442
480
|
const response = await this.fetchFn(`${this.baseURL}${path}`, {
|
|
443
481
|
method,
|
|
444
482
|
headers: this.headers,
|
|
445
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
483
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
484
|
+
signal
|
|
446
485
|
}).catch((err) => {
|
|
447
486
|
throw new ConnectionError(
|
|
448
487
|
err instanceof Error ? err.message : "Failed to connect"
|
|
@@ -451,13 +490,21 @@ var AstralformClient = class {
|
|
|
451
490
|
await this.handleError(response);
|
|
452
491
|
return response;
|
|
453
492
|
}
|
|
493
|
+
// DO NOT refactor these back into `request()` + `.json()`. Parsing the body
|
|
494
|
+
// INSIDE the raced callback is the entire fix: `json()` outside the deadline
|
|
495
|
+
// is the original bug (headers arrive, body stalls, caller hangs forever).
|
|
496
|
+
// `request()` survives for `del()`, which never reads the body.
|
|
454
497
|
async get(path) {
|
|
455
|
-
|
|
456
|
-
|
|
498
|
+
return this.withDeadline(async (signal) => {
|
|
499
|
+
const response = await this.send("GET", path, void 0, signal);
|
|
500
|
+
return await response.json();
|
|
501
|
+
});
|
|
457
502
|
}
|
|
458
503
|
async post(path, body) {
|
|
459
|
-
|
|
460
|
-
|
|
504
|
+
return this.withDeadline(async (signal) => {
|
|
505
|
+
const response = await this.send("POST", path, body, signal);
|
|
506
|
+
return await response.json();
|
|
507
|
+
});
|
|
461
508
|
}
|
|
462
509
|
async del(path) {
|
|
463
510
|
await this.request("DELETE", path);
|
|
@@ -1348,6 +1395,7 @@ var ChatSession = class {
|
|
|
1348
1395
|
upload_ids: options?.uploadIds,
|
|
1349
1396
|
agent_name: options?.agentName,
|
|
1350
1397
|
plan_mode: options?.planMode,
|
|
1398
|
+
goal: options?.goal,
|
|
1351
1399
|
// Per-request model choice (client-side model selection).
|
|
1352
1400
|
provider: options?.provider,
|
|
1353
1401
|
model: options?.model,
|
|
@@ -1911,6 +1959,7 @@ var StreamManager = class {
|
|
|
1911
1959
|
agentName: options?.agentName,
|
|
1912
1960
|
uploadIds: options?.uploadIds,
|
|
1913
1961
|
planMode: options?.planMode,
|
|
1962
|
+
goal: options?.goal,
|
|
1914
1963
|
provider: options?.provider,
|
|
1915
1964
|
model: options?.model,
|
|
1916
1965
|
reasoningEffort: options?.reasoningEffort,
|