@astralform/js 2.0.0 → 3.0.1

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.d.cts CHANGED
@@ -138,7 +138,7 @@ interface AstralformUserTokenConfig extends AstralformBaseConfig {
138
138
  */
139
139
  accessToken: string;
140
140
  /**
141
- * Active agent context. Sent as the `X-Project-ID` header (legacy wire name). The backend
141
+ * Active agent context. Sent as the `X-Agent-ID` header. The backend
142
142
  * verifies the token's developer has access to this agent on every
143
143
  * request; switching agents is a local `updateAgentId()` call.
144
144
  *
@@ -151,7 +151,7 @@ interface AstralformUserTokenConfig extends AstralformBaseConfig {
151
151
  /**
152
152
  * Optional end-user override — lets a developer acting under a user
153
153
  * token impersonate a downstream end-user identity for testing
154
- * purposes. When set, sent alongside `X-Project-ID` as `X-End-User-ID`
154
+ * purposes. When set, sent alongside `X-Agent-ID` as `X-End-User-ID`
155
155
  * so memory, rate limits, and conversations scope to the specified
156
156
  * end-user rather than the developer themselves.
157
157
  *
@@ -988,10 +988,11 @@ declare class ChatSession {
988
988
  switchConversation(id: string, jobId?: string,
989
989
  /**
990
990
  * User prompt that triggered this job, if known. Emitted as a
991
- * synthetic ``user_message`` ChatEvent right before the first
992
- * ``message_start`` of the replay. User messages aren't persisted
993
- * in ``job_events``, so without this the restored conversation
994
- * would show the agent response with no visible prompt above it.
991
+ * synthetic ``user_message`` ChatEvent at the START of the replay —
992
+ * a completed job maps to exactly one user turn, so every event in it
993
+ * belongs beneath that prompt. User messages aren't persisted in
994
+ * ``job_events``, so without this the restored conversation would show
995
+ * the agent response with no visible prompt above it.
995
996
  */
996
997
  userMessageContent?: string): Promise<void>;
997
998
  deleteConversation(id: string): Promise<void>;
@@ -1145,8 +1146,18 @@ interface RawSseEvent {
1145
1146
  declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
1146
1147
  /**
1147
1148
  * Replay persisted SSE events through the provided handler, interleaving
1148
- * user messages from session.messages at the first message_start of each
1149
- * turn (user messages aren't persisted in job_events).
1149
+ * user messages from session.messages at the START of each turn (user
1150
+ * messages aren't persisted in job_events).
1151
+ *
1152
+ * The turn boundary is a change of ``job_id``, NOT ``message_start`` or
1153
+ * ``message_stop``. A completed job maps to exactly one user turn — but a
1154
+ * single job can contain several ``message_start``/``message_stop`` pairs (a
1155
+ * tool-use loop: LLM call → tool result → LLM call again), so neither event
1156
+ * reliably delimits turns. And within a job some events precede the first
1157
+ * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
1158
+ * gating the user block on ``message_start`` would replay them above the
1159
+ * user's own message. Keying off ``job_id`` injects the prompt once per job,
1160
+ * before its first event — matching ``session.ts#switchConversation``.
1150
1161
  */
1151
1162
  declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
1152
1163
  role: string;
package/dist/index.d.ts CHANGED
@@ -138,7 +138,7 @@ interface AstralformUserTokenConfig extends AstralformBaseConfig {
138
138
  */
139
139
  accessToken: string;
140
140
  /**
141
- * Active agent context. Sent as the `X-Project-ID` header (legacy wire name). The backend
141
+ * Active agent context. Sent as the `X-Agent-ID` header. The backend
142
142
  * verifies the token's developer has access to this agent on every
143
143
  * request; switching agents is a local `updateAgentId()` call.
144
144
  *
@@ -151,7 +151,7 @@ interface AstralformUserTokenConfig extends AstralformBaseConfig {
151
151
  /**
152
152
  * Optional end-user override — lets a developer acting under a user
153
153
  * token impersonate a downstream end-user identity for testing
154
- * purposes. When set, sent alongside `X-Project-ID` as `X-End-User-ID`
154
+ * purposes. When set, sent alongside `X-Agent-ID` as `X-End-User-ID`
155
155
  * so memory, rate limits, and conversations scope to the specified
156
156
  * end-user rather than the developer themselves.
157
157
  *
@@ -988,10 +988,11 @@ declare class ChatSession {
988
988
  switchConversation(id: string, jobId?: string,
989
989
  /**
990
990
  * User prompt that triggered this job, if known. Emitted as a
991
- * synthetic ``user_message`` ChatEvent right before the first
992
- * ``message_start`` of the replay. User messages aren't persisted
993
- * in ``job_events``, so without this the restored conversation
994
- * would show the agent response with no visible prompt above it.
991
+ * synthetic ``user_message`` ChatEvent at the START of the replay —
992
+ * a completed job maps to exactly one user turn, so every event in it
993
+ * belongs beneath that prompt. User messages aren't persisted in
994
+ * ``job_events``, so without this the restored conversation would show
995
+ * the agent response with no visible prompt above it.
995
996
  */
996
997
  userMessageContent?: string): Promise<void>;
997
998
  deleteConversation(id: string): Promise<void>;
@@ -1145,8 +1146,18 @@ interface RawSseEvent {
1145
1146
  declare function mapSseToChat(raw: RawSseEvent): ChatEvent[];
1146
1147
  /**
1147
1148
  * Replay persisted SSE events through the provided handler, interleaving
1148
- * user messages from session.messages at the first message_start of each
1149
- * turn (user messages aren't persisted in job_events).
1149
+ * user messages from session.messages at the START of each turn (user
1150
+ * messages aren't persisted in job_events).
1151
+ *
1152
+ * The turn boundary is a change of ``job_id``, NOT ``message_start`` or
1153
+ * ``message_stop``. A completed job maps to exactly one user turn — but a
1154
+ * single job can contain several ``message_start``/``message_stop`` pairs (a
1155
+ * tool-use loop: LLM call → tool result → LLM call again), so neither event
1156
+ * reliably delimits turns. And within a job some events precede the first
1157
+ * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so
1158
+ * gating the user block on ``message_start`` would replay them above the
1159
+ * user's own message. Keying off ``job_id`` injects the prompt once per job,
1160
+ * before its first event — matching ``session.ts#switchConversation``.
1150
1161
  */
1151
1162
  declare function replayEvents(sseEvents: RawSseEvent[], userMessages: {
1152
1163
  role: string;
package/dist/index.js CHANGED
@@ -379,7 +379,7 @@ var AstralformClient = class {
379
379
  Authorization: `Bearer ${this.auth.accessToken}`
380
380
  };
381
381
  if (this.auth.agentId) {
382
- headers["X-Project-ID"] = this.auth.agentId;
382
+ headers["X-Agent-ID"] = this.auth.agentId;
383
383
  }
384
384
  if (this.auth.endUserId) {
385
385
  headers["X-End-User-ID"] = this.auth.endUserId;
@@ -434,10 +434,9 @@ var AstralformClient = class {
434
434
  async getHealth() {
435
435
  return this.get("/v1/health");
436
436
  }
437
- // Agent readiness check. The path is a legacy wire name (shared with the
438
- // iOS SDK) — it scopes to the client's active agent via X-Project-ID.
437
+ // Agent readiness check, scoped to the client's active agent via X-Agent-ID.
439
438
  async getAgentStatus() {
440
- const raw = await this.get("/v1/project/status");
439
+ const raw = await this.get("/v1/agent/status");
441
440
  const ui = raw.ui_components ?? {};
442
441
  return {
443
442
  isReady: raw.is_ready,
@@ -607,7 +606,7 @@ var AstralformClient = class {
607
606
  // --- Account-scoped discovery (user-token mode) ---
608
607
  //
609
608
  // Lets a signed-in user pick which team/agent they want to act on.
610
- // Backend gates these on OIDC user context (no X-Project-ID required) —
609
+ // Backend gates these on OIDC user context (no X-Agent-ID required) —
611
610
  // sending them in API-key mode yields 401.
612
611
  async listTeams() {
613
612
  const raw = await this.get("/v1/teams");
@@ -1629,17 +1628,12 @@ var ChatSession = class {
1629
1628
  ]);
1630
1629
  this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
1631
1630
  if (eventsResult.status === "fulfilled") {
1632
- let userMessageEmitted = !userMessageContent;
1631
+ if (userMessageContent) {
1632
+ this.emit({ type: "user_message", content: userMessageContent });
1633
+ }
1633
1634
  for (const ev of eventsResult.value) {
1634
1635
  const type = ev.data.type || ev.event;
1635
1636
  if (!type || type === "done") continue;
1636
- if (!userMessageEmitted && type === "message_start") {
1637
- this.emit({
1638
- type: "user_message",
1639
- content: userMessageContent
1640
- });
1641
- userMessageEmitted = true;
1642
- }
1643
1637
  const wire = { ...ev.data, type };
1644
1638
  try {
1645
1639
  await this.dispatchWireEvent(
@@ -1948,21 +1942,21 @@ function mapSseToChat(raw) {
1948
1942
  function replayEvents(sseEvents, userMessages, handleEvent, addBlock) {
1949
1943
  const userMsgs = userMessages.filter((m) => m.role === "user");
1950
1944
  let userIdx = 0;
1951
- let expectingUserMessage = true;
1945
+ let currentJobId = null;
1952
1946
  for (const raw of sseEvents) {
1953
1947
  const type = raw.data.type || raw.event;
1954
- if (type === "message_stop") {
1955
- expectingUserMessage = true;
1956
- }
1957
- if (type === "message_start" && expectingUserMessage && userIdx < userMsgs.length) {
1958
- const userMsg = userMsgs[userIdx];
1959
- addBlock({
1960
- type: "user",
1961
- id: `replay_user_${userIdx}`,
1962
- content: userMsg.content
1963
- });
1964
- userIdx++;
1965
- expectingUserMessage = false;
1948
+ if (!type || type === "done") continue;
1949
+ const jobId = raw.data.job_id ?? null;
1950
+ if (jobId !== null && jobId !== currentJobId) {
1951
+ currentJobId = jobId;
1952
+ if (userIdx < userMsgs.length) {
1953
+ addBlock({
1954
+ type: "user",
1955
+ id: `replay_user_${userIdx}`,
1956
+ content: userMsgs[userIdx].content
1957
+ });
1958
+ userIdx++;
1959
+ }
1966
1960
  }
1967
1961
  for (const ce of mapSseToChat(raw)) {
1968
1962
  handleEvent(ce);