@astralform/js 3.1.0 → 4.0.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 +109 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -16
- package/dist/index.d.ts +66 -16
- package/dist/index.js +109 -33
- 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
|
@@ -556,7 +556,12 @@ var AstralformClient = class {
|
|
|
556
556
|
thinking: m.thinking,
|
|
557
557
|
tools: m.tools,
|
|
558
558
|
vision: m.vision,
|
|
559
|
-
thinkingMode: m.thinking_mode
|
|
559
|
+
thinkingMode: m.thinking_mode,
|
|
560
|
+
// Coerce so the non-optional `supportsEffort: boolean` stays honest even
|
|
561
|
+
// against an older backend that omits `supports_effort` (→ false = safe:
|
|
562
|
+
// the effort control is hidden). Unlike the always-present siblings above,
|
|
563
|
+
// this field can be absent, so it's the one that needs coercion.
|
|
564
|
+
supportsEffort: Boolean(m.supports_effort)
|
|
560
565
|
}));
|
|
561
566
|
}
|
|
562
567
|
async getSkills() {
|
|
@@ -633,6 +638,10 @@ var AstralformClient = class {
|
|
|
633
638
|
workspacePath: raw.workspace_path,
|
|
634
639
|
sourceMessageId: raw.source_message_id,
|
|
635
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,
|
|
636
645
|
createdAt: raw.created_at
|
|
637
646
|
};
|
|
638
647
|
}
|
|
@@ -1338,7 +1347,6 @@ var ChatSession = class {
|
|
|
1338
1347
|
),
|
|
1339
1348
|
upload_ids: options?.uploadIds,
|
|
1340
1349
|
agent_name: options?.agentName,
|
|
1341
|
-
enable_search: options?.enableSearch,
|
|
1342
1350
|
plan_mode: options?.planMode,
|
|
1343
1351
|
// Per-request model choice (client-side model selection).
|
|
1344
1352
|
provider: options?.provider,
|
|
@@ -1348,15 +1356,14 @@ var ChatSession = class {
|
|
|
1348
1356
|
};
|
|
1349
1357
|
await this.processStream(request);
|
|
1350
1358
|
}
|
|
1351
|
-
async resendFromCheckpoint(messageId, newContent
|
|
1359
|
+
async resendFromCheckpoint(messageId, newContent) {
|
|
1352
1360
|
if (this.isStreaming) return;
|
|
1353
1361
|
const request = {
|
|
1354
1362
|
message: newContent,
|
|
1355
1363
|
conversation_id: this.conversationId ?? void 0,
|
|
1356
1364
|
resend_from: messageId,
|
|
1357
1365
|
mcp_manifest: this.toolRegistry.getManifest(),
|
|
1358
|
-
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1359
|
-
enable_search: options?.enableSearch
|
|
1366
|
+
enabled_mcp: Array.from(this.enabledClientTools)
|
|
1360
1367
|
};
|
|
1361
1368
|
await this.processStream(request);
|
|
1362
1369
|
}
|
|
@@ -1547,6 +1554,20 @@ var ChatSession = class {
|
|
|
1547
1554
|
}
|
|
1548
1555
|
}
|
|
1549
1556
|
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Synchronous replay of a single stored wire event — the side-effect +
|
|
1559
|
+
* translate + emit core of ``dispatchWireEvent`` without the (live-only)
|
|
1560
|
+
* client-tool round-trip. Called in a tight synchronous loop during history
|
|
1561
|
+
* restore so the consumer's per-event store writes batch into ONE render
|
|
1562
|
+
* instead of re-typing the whole conversation event by event.
|
|
1563
|
+
*/
|
|
1564
|
+
replayWireEvent(wire, conversationId) {
|
|
1565
|
+
this.applyWireSideEffects(wire, conversationId, "");
|
|
1566
|
+
const event = translateWireEvent(wire);
|
|
1567
|
+
if (event) {
|
|
1568
|
+
this.emit(event);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1550
1571
|
/**
|
|
1551
1572
|
* State mutations driven by wire events. Kept separate from translation so
|
|
1552
1573
|
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
@@ -1693,34 +1714,57 @@ var ChatSession = class {
|
|
|
1693
1714
|
this.messages = [];
|
|
1694
1715
|
return id;
|
|
1695
1716
|
}
|
|
1696
|
-
|
|
1717
|
+
/**
|
|
1718
|
+
* Replay one completed turn's already-fetched events, synchronously.
|
|
1719
|
+
*
|
|
1720
|
+
* Fetching is the caller's job (``StreamManager.restore`` loads every turn's
|
|
1721
|
+
* events in parallel and the message list once), so this is pure replay: no
|
|
1722
|
+
* awaits, so the whole restore runs in a single synchronous pass and the
|
|
1723
|
+
* consumer batches it into one render.
|
|
1724
|
+
*
|
|
1725
|
+
* ``userMessageContent`` is the prompt that triggered this turn. It's emitted
|
|
1726
|
+
* as a synthetic ``user_message`` BEFORE any of the turn's events: user
|
|
1727
|
+
* prompts aren't persisted in ``job_events``, and some events precede
|
|
1728
|
+
* ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
|
|
1729
|
+
* so leading with the prompt keeps the turn in order.
|
|
1730
|
+
*/
|
|
1731
|
+
replayTurn(id, events, userMessageContent) {
|
|
1697
1732
|
this.conversationId = id;
|
|
1698
1733
|
this.resetStreamingState();
|
|
1734
|
+
if (userMessageContent) {
|
|
1735
|
+
this.emit({ type: "user_message", content: userMessageContent });
|
|
1736
|
+
}
|
|
1737
|
+
for (const ev of events) {
|
|
1738
|
+
const type = ev.data.type || ev.event;
|
|
1739
|
+
if (!type || type === "done") continue;
|
|
1740
|
+
const wire = { ...ev.data, type };
|
|
1741
|
+
try {
|
|
1742
|
+
this.replayWireEvent(wire, id);
|
|
1743
|
+
} catch {
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Load a conversation's messages and replay its persisted history.
|
|
1749
|
+
*
|
|
1750
|
+
* Convenience for plain-``ChatSession`` consumers (the documented
|
|
1751
|
+
* conversation-management API). ``StreamManager`` drives restore itself —
|
|
1752
|
+
* loading messages once and replaying each turn in parallel — and does NOT
|
|
1753
|
+
* call this; it's kept so direct-Session usage doesn't break.
|
|
1754
|
+
*
|
|
1755
|
+
* Without ``jobId`` it replays the whole conversation; with one, just that
|
|
1756
|
+
* job's events.
|
|
1757
|
+
*/
|
|
1758
|
+
async switchConversation(id, jobId) {
|
|
1699
1759
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1700
1760
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1701
1761
|
this.client.getConversationEvents(id, jobId)
|
|
1702
1762
|
]);
|
|
1703
1763
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
for (const ev of eventsResult.value) {
|
|
1709
|
-
const type = ev.data.type || ev.event;
|
|
1710
|
-
if (!type || type === "done") continue;
|
|
1711
|
-
const wire = { ...ev.data, type };
|
|
1712
|
-
try {
|
|
1713
|
-
await this.dispatchWireEvent(
|
|
1714
|
-
wire,
|
|
1715
|
-
id,
|
|
1716
|
-
"",
|
|
1717
|
-
false
|
|
1718
|
-
// don't execute client tools on replay
|
|
1719
|
-
);
|
|
1720
|
-
} catch {
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1764
|
+
this.replayTurn(
|
|
1765
|
+
id,
|
|
1766
|
+
eventsResult.status === "fulfilled" ? eventsResult.value : []
|
|
1767
|
+
);
|
|
1724
1768
|
}
|
|
1725
1769
|
async deleteConversation(id) {
|
|
1726
1770
|
try {
|
|
@@ -1864,7 +1908,6 @@ var StreamManager = class {
|
|
|
1864
1908
|
this.setState("streaming");
|
|
1865
1909
|
try {
|
|
1866
1910
|
await this.session.send(content, {
|
|
1867
|
-
enableSearch: options?.enableSearch,
|
|
1868
1911
|
agentName: options?.agentName,
|
|
1869
1912
|
uploadIds: options?.uploadIds,
|
|
1870
1913
|
planMode: options?.planMode,
|
|
@@ -1896,8 +1939,26 @@ var StreamManager = class {
|
|
|
1896
1939
|
this.finalizeStream();
|
|
1897
1940
|
}
|
|
1898
1941
|
// ── Switch conversation ───────────────────────────────────────
|
|
1899
|
-
|
|
1942
|
+
/**
|
|
1943
|
+
* Switch the active conversation.
|
|
1944
|
+
*
|
|
1945
|
+
* ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a
|
|
1946
|
+
* restored conversation's rendered blocks: it moves the active pointer and
|
|
1947
|
+
* loads the message list (needed for send / regenerate context) but skips
|
|
1948
|
+
* the expensive event fetch + replay, and never enters the ``restoring``
|
|
1949
|
+
* state, so a consumer that clears its block view on ``restoring`` keeps
|
|
1950
|
+
* showing the cached history with no flash of a spinner.
|
|
1951
|
+
*
|
|
1952
|
+
* It still confirms there is no live job before skipping: the in-memory
|
|
1953
|
+
* background-job map is empty on a fresh instance (page reload) and blind to
|
|
1954
|
+
* jobs started in another tab/device, so the fast path always asks the server
|
|
1955
|
+
* (``getActiveJob``) and falls through to a full reconnect if one is running.
|
|
1956
|
+
* That one small request is the only cost it doesn't skip, so passing the
|
|
1957
|
+
* flag whenever you hold cached blocks is safe.
|
|
1958
|
+
*/
|
|
1959
|
+
async switchTo(conversationId, opts) {
|
|
1900
1960
|
if (conversationId === this._activeConversationId) return;
|
|
1961
|
+
const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);
|
|
1901
1962
|
if (this._state === "streaming") {
|
|
1902
1963
|
const oldConvId = this._activeConversationId;
|
|
1903
1964
|
const jobId = this.session.currentJobId;
|
|
@@ -1918,6 +1979,18 @@ var StreamManager = class {
|
|
|
1918
1979
|
});
|
|
1919
1980
|
}
|
|
1920
1981
|
this.setActiveConversation(conversationId);
|
|
1982
|
+
if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {
|
|
1983
|
+
let activeJobId = null;
|
|
1984
|
+
try {
|
|
1985
|
+
activeJobId = (await this.session.client.getActiveJob(conversationId)).jobId;
|
|
1986
|
+
} catch {
|
|
1987
|
+
}
|
|
1988
|
+
if (!activeJobId) {
|
|
1989
|
+
await this.session.loadConversation(conversationId);
|
|
1990
|
+
this.setState("idle");
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1921
1994
|
await this.restore(conversationId);
|
|
1922
1995
|
}
|
|
1923
1996
|
// ── Create / delete conversation ──────────────────────────────
|
|
@@ -1982,13 +2055,16 @@ var StreamManager = class {
|
|
|
1982
2055
|
const userMessages = this.session.messages.filter(
|
|
1983
2056
|
(m) => m.role === "user"
|
|
1984
2057
|
);
|
|
2058
|
+
const eventLists = await Promise.all(
|
|
2059
|
+
completedJobs.map(
|
|
2060
|
+
(job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
|
|
2061
|
+
)
|
|
2062
|
+
);
|
|
1985
2063
|
for (let i = 0; i < completedJobs.length; i++) {
|
|
1986
|
-
|
|
1987
|
-
const userContent = userMessages[i]?.content;
|
|
1988
|
-
await this.session.switchConversation(
|
|
2064
|
+
this.session.replayTurn(
|
|
1989
2065
|
conversationId,
|
|
1990
|
-
|
|
1991
|
-
|
|
2066
|
+
eventLists[i] ?? [],
|
|
2067
|
+
userMessages[i]?.content
|
|
1992
2068
|
);
|
|
1993
2069
|
}
|
|
1994
2070
|
if (completedJobs.length > 0) {
|