@astralform/js 9.2.0 → 9.4.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/dist/index.cjs +134 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +170 -1
- package/dist/index.d.ts +170 -1
- package/dist/index.js +133 -6
- 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";
|
|
@@ -442,6 +447,79 @@ var AstralformClient = class {
|
|
|
442
447
|
`/v1/code/projects/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
|
|
443
448
|
);
|
|
444
449
|
}
|
|
450
|
+
},
|
|
451
|
+
/**
|
|
452
|
+
* Code groups: a user-named container under one repository, for a feature
|
|
453
|
+
* whose work outlives a single session. Its sessions share one plan and one
|
|
454
|
+
* set of notes, so what one writes the others read.
|
|
455
|
+
*
|
|
456
|
+
* A group is a LABEL, not a binding — unlike a task's project, which is
|
|
457
|
+
* written once on the first turn. A task may join, move between, or leave
|
|
458
|
+
* groups at any time, and a deleted group's sessions survive as ungrouped.
|
|
459
|
+
*
|
|
460
|
+
* The server enforces three things worth knowing before you render a
|
|
461
|
+
* failure: a group that is not this user's answers `404` (never `403`, so a
|
|
462
|
+
* group id cannot be probed); filing a task into a group under a DIFFERENT
|
|
463
|
+
* repository than the task's answers `409`; and the group's plan and notes
|
|
464
|
+
* are deleted with the group, while each session keeps its own.
|
|
465
|
+
*/
|
|
466
|
+
groups: {
|
|
467
|
+
/**
|
|
468
|
+
* This user's groups, oldest first. Pass a repository to narrow to one
|
|
469
|
+
* repository's — the composer's picker does; the sidebar asks once and
|
|
470
|
+
* nests them itself.
|
|
471
|
+
*/
|
|
472
|
+
list: async (repository) => {
|
|
473
|
+
const query = repository ? `?repository=${encodeURIComponent(repository)}` : "";
|
|
474
|
+
const raw = await this.get(`/v1/code/groups${query}`);
|
|
475
|
+
return raw.map((g) => camelizeKeys(g));
|
|
476
|
+
},
|
|
477
|
+
/**
|
|
478
|
+
* Create a group under a repository. The repository goes through the same
|
|
479
|
+
* normaliser the Projects surface uses, so a pasted GitHub URL is accepted
|
|
480
|
+
* and anything that is not `owner/repo` is refused.
|
|
481
|
+
*/
|
|
482
|
+
create: async (repository, title) => {
|
|
483
|
+
const raw = await this.post("/v1/code/groups", { repository, title });
|
|
484
|
+
return camelizeKeys(raw);
|
|
485
|
+
},
|
|
486
|
+
/** Rename a group. A group that is not this user's answers `404`. */
|
|
487
|
+
rename: async (groupId, title) => {
|
|
488
|
+
const raw = await this.patch(`/v1/code/groups/${encodeURIComponent(groupId)}`, { title });
|
|
489
|
+
return camelizeKeys(raw);
|
|
490
|
+
},
|
|
491
|
+
/**
|
|
492
|
+
* Delete a group. Its tasks survive and become ungrouped, each keeping its
|
|
493
|
+
* own plan and notes; the group's SHARED documents are deleted with it.
|
|
494
|
+
*/
|
|
495
|
+
remove: async (groupId) => {
|
|
496
|
+
await this.del(`/v1/code/groups/${encodeURIComponent(groupId)}`);
|
|
497
|
+
},
|
|
498
|
+
/**
|
|
499
|
+
* File a task into a group. Idempotent — filing an already-filed task
|
|
500
|
+
* again is the same state, not an error. Refused with `409` when the task
|
|
501
|
+
* belongs to a different repository than the group, because it would
|
|
502
|
+
* render nowhere in the sidebar.
|
|
503
|
+
*/
|
|
504
|
+
assign: async (groupId, conversationId) => {
|
|
505
|
+
const raw = await this.post(
|
|
506
|
+
`/v1/code/groups/${encodeURIComponent(groupId)}/tasks/${encodeURIComponent(conversationId)}`,
|
|
507
|
+
void 0
|
|
508
|
+
);
|
|
509
|
+
return camelizeKeys(raw);
|
|
510
|
+
},
|
|
511
|
+
/**
|
|
512
|
+
* Take a task out of its group. The task and its documents are untouched.
|
|
513
|
+
*
|
|
514
|
+
* Deliberately not strict about WHICH group the task is in: a task that is
|
|
515
|
+
* in a different one (or in none) already satisfies "this task should not
|
|
516
|
+
* be grouped", so the call clears it rather than refusing.
|
|
517
|
+
*/
|
|
518
|
+
unassign: async (groupId, conversationId) => {
|
|
519
|
+
await this.del(
|
|
520
|
+
`/v1/code/groups/${encodeURIComponent(groupId)}/tasks/${encodeURIComponent(conversationId)}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
445
523
|
}
|
|
446
524
|
};
|
|
447
525
|
if (isApiKeyConfig(config)) {
|
|
@@ -642,6 +720,13 @@ var AstralformClient = class {
|
|
|
642
720
|
return await response.json();
|
|
643
721
|
});
|
|
644
722
|
}
|
|
723
|
+
/** A GET whose body is binary. Read inside the race, for the reason above. */
|
|
724
|
+
async getBlob(path) {
|
|
725
|
+
return this.withDeadline(async (signal) => {
|
|
726
|
+
const response = await this.send("GET", path, void 0, signal);
|
|
727
|
+
return response.blob();
|
|
728
|
+
});
|
|
729
|
+
}
|
|
645
730
|
async post(path, body) {
|
|
646
731
|
return this.withDeadline(async (signal) => {
|
|
647
732
|
const response = await this.send("POST", path, body, signal);
|
|
@@ -861,6 +946,7 @@ var AstralformClient = class {
|
|
|
861
946
|
const params = new URLSearchParams();
|
|
862
947
|
if (jobId) params.set("job_id", jobId);
|
|
863
948
|
if (options?.toolOutputs === "stub") params.set("tool_outputs", "stub");
|
|
949
|
+
if (options?.toolImages === "stub") params.set("tool_images", "stub");
|
|
864
950
|
const query = params.toString();
|
|
865
951
|
return this.get(
|
|
866
952
|
`/v1/conversations/${encodeURIComponent(conversationId)}/events${query ? `?${query}` : ""}`
|
|
@@ -875,6 +961,25 @@ var AstralformClient = class {
|
|
|
875
961
|
);
|
|
876
962
|
return res.output;
|
|
877
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* The image behind a {@link ToolImageStub}, as a `Blob` of the preview's bytes.
|
|
966
|
+
*
|
|
967
|
+
* Takes the stub itself and nothing else — the `job_id` scoping that
|
|
968
|
+
* `getToolOutput`'s id form warns about cannot be dropped from a value that
|
|
969
|
+
* carries it. There is deliberately no by-ids form.
|
|
970
|
+
*
|
|
971
|
+
* A `Blob` rather than a URL because the route is authenticated: an `<img
|
|
972
|
+
* src>` cannot send the bearer token, so a browser consumer turns this into
|
|
973
|
+
* an object URL (`URL.createObjectURL`) and revokes it when the image
|
|
974
|
+
* unmounts. The response is `private, immutable`, so a repeat fetch of the
|
|
975
|
+
* same preview is served by the HTTP cache.
|
|
976
|
+
*/
|
|
977
|
+
async getToolImage(conversationId, stub) {
|
|
978
|
+
const query = stub.job_id ? `?job_id=${encodeURIComponent(stub.job_id)}` : "";
|
|
979
|
+
return this.getBlob(
|
|
980
|
+
`/v1/conversations/${encodeURIComponent(conversationId)}/tool-output/${encodeURIComponent(stub.call_id)}/images/${stub.index}${query}`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
878
983
|
async submitToolResult(request) {
|
|
879
984
|
await this.post("/v1/tool-result", request);
|
|
880
985
|
}
|
|
@@ -2749,6 +2854,12 @@ var StreamManager = class _StreamManager {
|
|
|
2749
2854
|
* Defaults to inline, so this is inert until a consumer opts in.
|
|
2750
2855
|
*/
|
|
2751
2856
|
this.toolOutputs = "inline";
|
|
2857
|
+
/**
|
|
2858
|
+
* Whether restore asks for tool image previews inline or as fetch handles.
|
|
2859
|
+
* Separate from `toolOutputs` because the server keeps them separate — see
|
|
2860
|
+
* `ToolImageStub` — and on the same one-setting-both-waves rule.
|
|
2861
|
+
*/
|
|
2862
|
+
this.toolImages = "inline";
|
|
2752
2863
|
this._state = "idle";
|
|
2753
2864
|
this._activeConversationId = null;
|
|
2754
2865
|
this._backgroundJobs = /* @__PURE__ */ new Map();
|
|
@@ -2805,6 +2916,25 @@ var StreamManager = class _StreamManager {
|
|
|
2805
2916
|
setToolOutputMode(mode) {
|
|
2806
2917
|
this.toolOutputs = mode;
|
|
2807
2918
|
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Ask restore for stubbed image previews, fetched on demand.
|
|
2921
|
+
*
|
|
2922
|
+
* The same caution as `setToolOutputMode`, sharper: a consumer that cannot
|
|
2923
|
+
* resolve one (`isToolImageStub`, `client.getToolImage`) finds an `images`
|
|
2924
|
+
* entry with no `url` and draws a broken image where the preview was.
|
|
2925
|
+
*/
|
|
2926
|
+
setToolImageMode(mode) {
|
|
2927
|
+
this.toolImages = mode;
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* The ONE options object both event waves fetch with — the newest page and
|
|
2931
|
+
* every `loadEarlierTurns` page. Built in one place so an opt-in added here
|
|
2932
|
+
* reaches both by construction: two literals at two call sites is how one
|
|
2933
|
+
* wave would silently keep asking for inline payloads.
|
|
2934
|
+
*/
|
|
2935
|
+
get eventFetchOptions() {
|
|
2936
|
+
return { toolOutputs: this.toolOutputs, toolImages: this.toolImages };
|
|
2937
|
+
}
|
|
2808
2938
|
emit(event) {
|
|
2809
2939
|
for (const handler of this.handlers) {
|
|
2810
2940
|
try {
|
|
@@ -3305,9 +3435,7 @@ var StreamManager = class _StreamManager {
|
|
|
3305
3435
|
const boundary = this.oldestDrawnMessageId;
|
|
3306
3436
|
const eventLists = await Promise.all(
|
|
3307
3437
|
page.jobs.map(
|
|
3308
|
-
(job) => session.client.getConversationEvents(conversationId, job.job_id,
|
|
3309
|
-
toolOutputs: this.toolOutputs
|
|
3310
|
-
}).catch(() => [])
|
|
3438
|
+
(job) => session.client.getConversationEvents(conversationId, job.job_id, this.eventFetchOptions).catch(() => [])
|
|
3311
3439
|
)
|
|
3312
3440
|
);
|
|
3313
3441
|
if (stop()) return 0;
|
|
@@ -3428,9 +3556,7 @@ var StreamManager = class _StreamManager {
|
|
|
3428
3556
|
});
|
|
3429
3557
|
const eventLists = await Promise.all(
|
|
3430
3558
|
replayableJobs.map(
|
|
3431
|
-
(job) => this.session.client.getConversationEvents(conversationId, job.job_id,
|
|
3432
|
-
toolOutputs: this.toolOutputs
|
|
3433
|
-
}).catch(() => [])
|
|
3559
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id, this.eventFetchOptions).catch(() => [])
|
|
3434
3560
|
)
|
|
3435
3561
|
);
|
|
3436
3562
|
if (stopReplay()) return false;
|
|
@@ -3578,6 +3704,7 @@ export {
|
|
|
3578
3704
|
VOICE_POLISH_MODES,
|
|
3579
3705
|
generateId,
|
|
3580
3706
|
isEmbeddedResource,
|
|
3707
|
+
isToolImageStub,
|
|
3581
3708
|
isToolOutputStub,
|
|
3582
3709
|
isVoiceLLMMode,
|
|
3583
3710
|
isVoicePolishMode,
|