@astralform/js 9.1.0 → 9.3.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 +20 -2
- package/dist/index.cjs +74 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -5
- package/dist/index.d.ts +198 -5
- package/dist/index.js +73 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -319,6 +319,11 @@ function isToolOutputStub(value) {
|
|
|
319
319
|
const v = value;
|
|
320
320
|
return v.__stub === "tool_output" && typeof v.call_id === "string" && v.call_id.length > 0;
|
|
321
321
|
}
|
|
322
|
+
function isToolImageStub(value) {
|
|
323
|
+
if (typeof value !== "object" || value === null) return false;
|
|
324
|
+
const v = value;
|
|
325
|
+
return v.__stub === "tool_image" && typeof v.call_id === "string" && v.call_id.length > 0 && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 0;
|
|
326
|
+
}
|
|
322
327
|
|
|
323
328
|
// src/client.ts
|
|
324
329
|
var DEFAULT_BASE_URL = "https://api.astralform.ai";
|
|
@@ -642,6 +647,13 @@ var AstralformClient = class {
|
|
|
642
647
|
return await response.json();
|
|
643
648
|
});
|
|
644
649
|
}
|
|
650
|
+
/** A GET whose body is binary. Read inside the race, for the reason above. */
|
|
651
|
+
async getBlob(path) {
|
|
652
|
+
return this.withDeadline(async (signal) => {
|
|
653
|
+
const response = await this.send("GET", path, void 0, signal);
|
|
654
|
+
return response.blob();
|
|
655
|
+
});
|
|
656
|
+
}
|
|
645
657
|
async post(path, body) {
|
|
646
658
|
return this.withDeadline(async (signal) => {
|
|
647
659
|
const response = await this.send("POST", path, body, signal);
|
|
@@ -861,6 +873,7 @@ var AstralformClient = class {
|
|
|
861
873
|
const params = new URLSearchParams();
|
|
862
874
|
if (jobId) params.set("job_id", jobId);
|
|
863
875
|
if (options?.toolOutputs === "stub") params.set("tool_outputs", "stub");
|
|
876
|
+
if (options?.toolImages === "stub") params.set("tool_images", "stub");
|
|
864
877
|
const query = params.toString();
|
|
865
878
|
return this.get(
|
|
866
879
|
`/v1/conversations/${encodeURIComponent(conversationId)}/events${query ? `?${query}` : ""}`
|
|
@@ -875,6 +888,25 @@ var AstralformClient = class {
|
|
|
875
888
|
);
|
|
876
889
|
return res.output;
|
|
877
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* The image behind a {@link ToolImageStub}, as a `Blob` of the preview's bytes.
|
|
893
|
+
*
|
|
894
|
+
* Takes the stub itself and nothing else — the `job_id` scoping that
|
|
895
|
+
* `getToolOutput`'s id form warns about cannot be dropped from a value that
|
|
896
|
+
* carries it. There is deliberately no by-ids form.
|
|
897
|
+
*
|
|
898
|
+
* A `Blob` rather than a URL because the route is authenticated: an `<img
|
|
899
|
+
* src>` cannot send the bearer token, so a browser consumer turns this into
|
|
900
|
+
* an object URL (`URL.createObjectURL`) and revokes it when the image
|
|
901
|
+
* unmounts. The response is `private, immutable`, so a repeat fetch of the
|
|
902
|
+
* same preview is served by the HTTP cache.
|
|
903
|
+
*/
|
|
904
|
+
async getToolImage(conversationId, stub) {
|
|
905
|
+
const query = stub.job_id ? `?job_id=${encodeURIComponent(stub.job_id)}` : "";
|
|
906
|
+
return this.getBlob(
|
|
907
|
+
`/v1/conversations/${encodeURIComponent(conversationId)}/tool-output/${encodeURIComponent(stub.call_id)}/images/${stub.index}${query}`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
878
910
|
async submitToolResult(request) {
|
|
879
911
|
await this.post("/v1/tool-result", request);
|
|
880
912
|
}
|
|
@@ -977,7 +1009,11 @@ var AstralformClient = class {
|
|
|
977
1009
|
// `VoicePolishMode`; `structured` is the server's own default.
|
|
978
1010
|
defaultMode: isVoicePolishMode(raw.default_mode) ? raw.default_mode : "structured",
|
|
979
1011
|
silenceAutoStopSeconds: raw.silence_auto_stop_seconds ?? 2,
|
|
980
|
-
|
|
1012
|
+
// Deliberately not read from the wire. Auto-send was removed from the
|
|
1013
|
+
// platform (2026-09-14) and the server pins `auto_send` to false; an
|
|
1014
|
+
// older server still sending `true` must not make a client auto-send, so
|
|
1015
|
+
// an absent field and an explicit `true` both read as false.
|
|
1016
|
+
autoSend: false,
|
|
981
1017
|
maxRecordingSeconds: raw.max_recording_seconds ?? 300,
|
|
982
1018
|
supportsStreaming: Boolean(raw.supports_streaming),
|
|
983
1019
|
hotwords: raw.hotwords ?? []
|
|
@@ -2745,6 +2781,12 @@ var StreamManager = class _StreamManager {
|
|
|
2745
2781
|
* Defaults to inline, so this is inert until a consumer opts in.
|
|
2746
2782
|
*/
|
|
2747
2783
|
this.toolOutputs = "inline";
|
|
2784
|
+
/**
|
|
2785
|
+
* Whether restore asks for tool image previews inline or as fetch handles.
|
|
2786
|
+
* Separate from `toolOutputs` because the server keeps them separate — see
|
|
2787
|
+
* `ToolImageStub` — and on the same one-setting-both-waves rule.
|
|
2788
|
+
*/
|
|
2789
|
+
this.toolImages = "inline";
|
|
2748
2790
|
this._state = "idle";
|
|
2749
2791
|
this._activeConversationId = null;
|
|
2750
2792
|
this._backgroundJobs = /* @__PURE__ */ new Map();
|
|
@@ -2801,6 +2843,25 @@ var StreamManager = class _StreamManager {
|
|
|
2801
2843
|
setToolOutputMode(mode) {
|
|
2802
2844
|
this.toolOutputs = mode;
|
|
2803
2845
|
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Ask restore for stubbed image previews, fetched on demand.
|
|
2848
|
+
*
|
|
2849
|
+
* The same caution as `setToolOutputMode`, sharper: a consumer that cannot
|
|
2850
|
+
* resolve one (`isToolImageStub`, `client.getToolImage`) finds an `images`
|
|
2851
|
+
* entry with no `url` and draws a broken image where the preview was.
|
|
2852
|
+
*/
|
|
2853
|
+
setToolImageMode(mode) {
|
|
2854
|
+
this.toolImages = mode;
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* The ONE options object both event waves fetch with — the newest page and
|
|
2858
|
+
* every `loadEarlierTurns` page. Built in one place so an opt-in added here
|
|
2859
|
+
* reaches both by construction: two literals at two call sites is how one
|
|
2860
|
+
* wave would silently keep asking for inline payloads.
|
|
2861
|
+
*/
|
|
2862
|
+
get eventFetchOptions() {
|
|
2863
|
+
return { toolOutputs: this.toolOutputs, toolImages: this.toolImages };
|
|
2864
|
+
}
|
|
2804
2865
|
emit(event) {
|
|
2805
2866
|
for (const handler of this.handlers) {
|
|
2806
2867
|
try {
|
|
@@ -3301,9 +3362,7 @@ var StreamManager = class _StreamManager {
|
|
|
3301
3362
|
const boundary = this.oldestDrawnMessageId;
|
|
3302
3363
|
const eventLists = await Promise.all(
|
|
3303
3364
|
page.jobs.map(
|
|
3304
|
-
(job) => session.client.getConversationEvents(conversationId, job.job_id,
|
|
3305
|
-
toolOutputs: this.toolOutputs
|
|
3306
|
-
}).catch(() => [])
|
|
3365
|
+
(job) => session.client.getConversationEvents(conversationId, job.job_id, this.eventFetchOptions).catch(() => [])
|
|
3307
3366
|
)
|
|
3308
3367
|
);
|
|
3309
3368
|
if (stop()) return 0;
|
|
@@ -3362,7 +3421,13 @@ var StreamManager = class _StreamManager {
|
|
|
3362
3421
|
conversationId,
|
|
3363
3422
|
position: "prepend",
|
|
3364
3423
|
hasMore: completed ? page.hasMore : true,
|
|
3365
|
-
complete: completed
|
|
3424
|
+
complete: completed,
|
|
3425
|
+
// Empty on the incomplete path, matching the blocks a consumer is
|
|
3426
|
+
// required to discard there: that page's cursor did not advance, so
|
|
3427
|
+
// these same turns replay from the start on the next request and
|
|
3428
|
+
// rehydrating from them now would key state to turns about to be
|
|
3429
|
+
// re-emitted.
|
|
3430
|
+
jobs: completed ? page.jobs : []
|
|
3366
3431
|
});
|
|
3367
3432
|
return emitted;
|
|
3368
3433
|
} finally {
|
|
@@ -3418,9 +3483,7 @@ var StreamManager = class _StreamManager {
|
|
|
3418
3483
|
});
|
|
3419
3484
|
const eventLists = await Promise.all(
|
|
3420
3485
|
replayableJobs.map(
|
|
3421
|
-
(job) => this.session.client.getConversationEvents(conversationId, job.job_id,
|
|
3422
|
-
toolOutputs: this.toolOutputs
|
|
3423
|
-
}).catch(() => [])
|
|
3486
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id, this.eventFetchOptions).catch(() => [])
|
|
3424
3487
|
)
|
|
3425
3488
|
);
|
|
3426
3489
|
if (stopReplay()) return false;
|
|
@@ -3451,7 +3514,7 @@ var StreamManager = class _StreamManager {
|
|
|
3451
3514
|
this.oldestDrawnMessageId = plan[0]?.messageId ?? null;
|
|
3452
3515
|
complete = true;
|
|
3453
3516
|
if (stopReplay()) return false;
|
|
3454
|
-
this.emit({ type: "restoreSettled", conversationId });
|
|
3517
|
+
this.emit({ type: "restoreSettled", conversationId, jobs });
|
|
3455
3518
|
const versionCount = replayableJobs.filter(
|
|
3456
3519
|
(j) => j.status === "completed"
|
|
3457
3520
|
).length;
|
|
@@ -3568,6 +3631,7 @@ export {
|
|
|
3568
3631
|
VOICE_POLISH_MODES,
|
|
3569
3632
|
generateId,
|
|
3570
3633
|
isEmbeddedResource,
|
|
3634
|
+
isToolImageStub,
|
|
3571
3635
|
isToolOutputStub,
|
|
3572
3636
|
isVoiceLLMMode,
|
|
3573
3637
|
isVoicePolishMode,
|