@astralform/js 3.2.0 → 4.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/README.md +1 -2
- package/dist/index.cjs +105 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -16
- package/dist/index.d.ts +81 -16
- package/dist/index.js +105 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -283,11 +283,10 @@ session.toggleClientTool("mcp_get_current_time"); // returns true if now enabled
|
|
|
283
283
|
session.enabledClientTools; // Set<string>
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
Platform-level features (
|
|
286
|
+
Platform-level features (e.g. plan mode) are enabled per-request via the `send` options. Web search needs no option: when the agent's search feature is enabled server-side, the agent decides per-task whether to search.
|
|
287
287
|
|
|
288
288
|
```ts
|
|
289
289
|
await session.send("Research the latest on WebGPU", {
|
|
290
|
-
enableSearch: true,
|
|
291
290
|
planMode: true,
|
|
292
291
|
});
|
|
293
292
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -638,6 +638,10 @@ var AstralformClient = class {
|
|
|
638
638
|
workspacePath: raw.workspace_path,
|
|
639
639
|
sourceMessageId: raw.source_message_id,
|
|
640
640
|
agentName: raw.agent_name,
|
|
641
|
+
// The API serializes an unsigned asset as `url: null`; normalize to
|
|
642
|
+
// undefined so it matches the `url?: string` type and consumers that
|
|
643
|
+
// check `!== undefined` never receive a null.
|
|
644
|
+
url: raw.url ?? void 0,
|
|
641
645
|
createdAt: raw.created_at
|
|
642
646
|
};
|
|
643
647
|
}
|
|
@@ -1343,8 +1347,8 @@ var ChatSession = class {
|
|
|
1343
1347
|
),
|
|
1344
1348
|
upload_ids: options?.uploadIds,
|
|
1345
1349
|
agent_name: options?.agentName,
|
|
1346
|
-
enable_search: options?.enableSearch,
|
|
1347
1350
|
plan_mode: options?.planMode,
|
|
1351
|
+
goal: options?.goal,
|
|
1348
1352
|
// Per-request model choice (client-side model selection).
|
|
1349
1353
|
provider: options?.provider,
|
|
1350
1354
|
model: options?.model,
|
|
@@ -1353,15 +1357,14 @@ var ChatSession = class {
|
|
|
1353
1357
|
};
|
|
1354
1358
|
await this.processStream(request);
|
|
1355
1359
|
}
|
|
1356
|
-
async resendFromCheckpoint(messageId, newContent
|
|
1360
|
+
async resendFromCheckpoint(messageId, newContent) {
|
|
1357
1361
|
if (this.isStreaming) return;
|
|
1358
1362
|
const request = {
|
|
1359
1363
|
message: newContent,
|
|
1360
1364
|
conversation_id: this.conversationId ?? void 0,
|
|
1361
1365
|
resend_from: messageId,
|
|
1362
1366
|
mcp_manifest: this.toolRegistry.getManifest(),
|
|
1363
|
-
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1364
|
-
enable_search: options?.enableSearch
|
|
1367
|
+
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1365
1368
|
};
|
|
1366
1369
|
await this.processStream(request);
|
|
1367
1370
|
}
|
|
@@ -1552,6 +1555,20 @@ var ChatSession = class {
|
|
|
1552
1555
|
}
|
|
1553
1556
|
}
|
|
1554
1557
|
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Synchronous replay of a single stored wire event — the side-effect +
|
|
1560
|
+
* translate + emit core of ``dispatchWireEvent`` without the (live-only)
|
|
1561
|
+
* client-tool round-trip. Called in a tight synchronous loop during history
|
|
1562
|
+
* restore so the consumer's per-event store writes batch into ONE render
|
|
1563
|
+
* instead of re-typing the whole conversation event by event.
|
|
1564
|
+
*/
|
|
1565
|
+
replayWireEvent(wire, conversationId) {
|
|
1566
|
+
this.applyWireSideEffects(wire, conversationId, "");
|
|
1567
|
+
const event = translateWireEvent(wire);
|
|
1568
|
+
if (event) {
|
|
1569
|
+
this.emit(event);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1555
1572
|
/**
|
|
1556
1573
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1557
1574
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1698,34 +1715,57 @@ var ChatSession = class {
|
|
|
1698
1715
|
this.messages = [];
|
|
1699
1716
|
return id;
|
|
1700
1717
|
}
|
|
1701
|
-
|
|
1718
|
+
/**
|
|
1719
|
+
* Replay one completed turn's already-fetched events, synchronously.
|
|
1720
|
+
*
|
|
1721
|
+
* Fetching is the caller's job (``StreamManager.restore`` loads every turn's
|
|
1722
|
+
* events in parallel and the message list once), so this is pure replay: no
|
|
1723
|
+
* awaits, so the whole restore runs in a single synchronous pass and the
|
|
1724
|
+
* consumer batches it into one render.
|
|
1725
|
+
*
|
|
1726
|
+
* ``userMessageContent`` is the prompt that triggered this turn. It's emitted
|
|
1727
|
+
* as a synthetic ``user_message`` BEFORE any of the turn's events: user
|
|
1728
|
+
* prompts aren't persisted in ``job_events``, and some events precede
|
|
1729
|
+
* ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
|
|
1730
|
+
* so leading with the prompt keeps the turn in order.
|
|
1731
|
+
*/
|
|
1732
|
+
replayTurn(id, events, userMessageContent) {
|
|
1702
1733
|
this.conversationId = id;
|
|
1703
1734
|
this.resetStreamingState();
|
|
1735
|
+
if (userMessageContent) {
|
|
1736
|
+
this.emit({ type: "user_message", content: userMessageContent });
|
|
1737
|
+
}
|
|
1738
|
+
for (const ev of events) {
|
|
1739
|
+
const type = ev.data.type || ev.event;
|
|
1740
|
+
if (!type || type === "done") continue;
|
|
1741
|
+
const wire = { ...ev.data, type };
|
|
1742
|
+
try {
|
|
1743
|
+
this.replayWireEvent(wire, id);
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Load a conversation's messages and replay its persisted history.
|
|
1750
|
+
*
|
|
1751
|
+
* Convenience for plain-``ChatSession`` consumers (the documented
|
|
1752
|
+
* conversation-management API). ``StreamManager`` drives restore itself —
|
|
1753
|
+
* loading messages once and replaying each turn in parallel — and does NOT
|
|
1754
|
+
* call this; it's kept so direct-Session usage doesn't break.
|
|
1755
|
+
*
|
|
1756
|
+
* Without ``jobId`` it replays the whole conversation; with one, just that
|
|
1757
|
+
* job's events.
|
|
1758
|
+
*/
|
|
1759
|
+
async switchConversation(id, jobId) {
|
|
1704
1760
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1705
1761
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1706
1762
|
this.client.getConversationEvents(id, jobId)
|
|
1707
1763
|
]);
|
|
1708
1764
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
for (const ev of eventsResult.value) {
|
|
1714
|
-
const type = ev.data.type || ev.event;
|
|
1715
|
-
if (!type || type === "done") continue;
|
|
1716
|
-
const wire = { ...ev.data, type };
|
|
1717
|
-
try {
|
|
1718
|
-
await this.dispatchWireEvent(
|
|
1719
|
-
wire,
|
|
1720
|
-
id,
|
|
1721
|
-
"",
|
|
1722
|
-
false
|
|
1723
|
-
// don't execute client tools on replay
|
|
1724
|
-
);
|
|
1725
|
-
} catch {
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1765
|
+
this.replayTurn(
|
|
1766
|
+
id,
|
|
1767
|
+
eventsResult.status === "fulfilled" ? eventsResult.value : []
|
|
1768
|
+
);
|
|
1729
1769
|
}
|
|
1730
1770
|
async deleteConversation(id) {
|
|
1731
1771
|
try {
|
|
@@ -1869,10 +1909,10 @@ var StreamManager = class {
|
|
|
1869
1909
|
this.setState("streaming");
|
|
1870
1910
|
try {
|
|
1871
1911
|
await this.session.send(content, {
|
|
1872
|
-
enableSearch: options?.enableSearch,
|
|
1873
1912
|
agentName: options?.agentName,
|
|
1874
1913
|
uploadIds: options?.uploadIds,
|
|
1875
1914
|
planMode: options?.planMode,
|
|
1915
|
+
goal: options?.goal,
|
|
1876
1916
|
provider: options?.provider,
|
|
1877
1917
|
model: options?.model,
|
|
1878
1918
|
reasoningEffort: options?.reasoningEffort,
|
|
@@ -1901,8 +1941,26 @@ var StreamManager = class {
|
|
|
1901
1941
|
this.finalizeStream();
|
|
1902
1942
|
}
|
|
1903
1943
|
// ── Switch conversation ───────────────────────────────────────
|
|
1904
|
-
|
|
1944
|
+
/**
|
|
1945
|
+
* Switch the active conversation.
|
|
1946
|
+
*
|
|
1947
|
+
* ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
|
|
1948
|
+
* restored conversation's rendered blocks: it moves the active pointer and
|
|
1949
|
+
* loads the message list (needed for send / regenerate context) but skips
|
|
1950
|
+
* the expensive event fetch + replay, and never enters the ``restoring``
|
|
1951
|
+
* state, so a consumer that clears its block view on ``restoring`` keeps
|
|
1952
|
+
* showing the cached history with no flash of a spinner.
|
|
1953
|
+
*
|
|
1954
|
+
* It still confirms there is no live job before skipping: the in-memory
|
|
1955
|
+
* background-job map is empty on a fresh instance (page reload) and blind to
|
|
1956
|
+
* jobs started in another tab/device, so the fast path always asks the server
|
|
1957
|
+
* (``getActiveJob``) and falls through to a full reconnect if one is running.
|
|
1958
|
+
* That one small request is the only cost it doesn't skip, so passing the
|
|
1959
|
+
* flag whenever you hold cached blocks is safe.
|
|
1960
|
+
*/
|
|
1961
|
+
async switchTo(conversationId, opts) {
|
|
1905
1962
|
if (conversationId === this._activeConversationId) return;
|
|
1963
|
+
const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);
|
|
1906
1964
|
if (this._state === "streaming") {
|
|
1907
1965
|
const oldConvId = this._activeConversationId;
|
|
1908
1966
|
const jobId = this.session.currentJobId;
|
|
@@ -1923,6 +1981,18 @@ var StreamManager = class {
|
|
|
1923
1981
|
});
|
|
1924
1982
|
}
|
|
1925
1983
|
this.setActiveConversation(conversationId);
|
|
1984
|
+
if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {
|
|
1985
|
+
let activeJobId = null;
|
|
1986
|
+
try {
|
|
1987
|
+
activeJobId = (await this.session.client.getActiveJob(conversationId)).jobId;
|
|
1988
|
+
} catch {
|
|
1989
|
+
}
|
|
1990
|
+
if (!activeJobId) {
|
|
1991
|
+
await this.session.loadConversation(conversationId);
|
|
1992
|
+
this.setState("idle");
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1926
1996
|
await this.restore(conversationId);
|
|
1927
1997
|
}
|
|
1928
1998
|
// ── Create / delete conversation ──────────────────────────────
|
|
@@ -1987,13 +2057,16 @@ var StreamManager = class {
|
|
|
1987
2057
|
const userMessages = this.session.messages.filter(
|
|
1988
2058
|
(m) => m.role === "user"
|
|
1989
2059
|
);
|
|
2060
|
+
const eventLists = await Promise.all(
|
|
2061
|
+
completedJobs.map(
|
|
2062
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
|
|
2063
|
+
)
|
|
2064
|
+
);
|
|
1990
2065
|
for (let i = 0; i < completedJobs.length; i++) {
|
|
1991
|
-
|
|
1992
|
-
const userContent = userMessages[i]?.content;
|
|
1993
|
-
await this.session.switchConversation(
|
|
2066
|
+
this.session.replayTurn(
|
|
1994
2067
|
conversationId,
|
|
1995
|
-
|
|
1996
|
-
|
|
2068
|
+
eventLists[i] ?? [],
|
|
2069
|
+
userMessages[i]?.content
|
|
1997
2070
|
);
|
|
1998
2071
|
}
|
|
1999
2072
|
if (completedJobs.length > 0) {
|