@agentforge-io/chat-sdk 2.0.15 → 2.0.17

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.
@@ -4,6 +4,34 @@
4
4
  * and easy to reason about.
5
5
  */
6
6
  export type ChatRole = 'user' | 'assistant' | 'system';
7
+ /**
8
+ * Structured payload attached to messages the server tagged with a
9
+ * non-text intent. Today's kinds:
10
+ *
11
+ * - `awaiting_approval`: a tool the agent wanted to run requires
12
+ * human approval. The View layer renders an Approve/Deny card
13
+ * using `approvalId` + `toolName`. After Approve the client should
14
+ * auto-send a "continue" follow-up so the gate's fast-path consumes
15
+ * the pending row and the turn finishes.
16
+ * - `tool_blocked`: a tool was hard-blocked by the gate. Terminal,
17
+ * no action — render as an error card.
18
+ *
19
+ * Future kinds extend this union without breaking back-compat: the
20
+ * View checks `kind` and falls back to plain text when it's unknown.
21
+ */
22
+ export type ChatMessageMetadata = {
23
+ kind: 'awaiting_approval';
24
+ approvalId: string;
25
+ toolName: string;
26
+ expiresAt: string;
27
+ } | {
28
+ kind: 'tool_blocked';
29
+ toolName: string;
30
+ reason?: string;
31
+ } | {
32
+ kind: string;
33
+ [k: string]: unknown;
34
+ };
7
35
  export interface ChatMessage {
8
36
  /** Stable client-side id so view layers can key off it. */
9
37
  id: string;
@@ -15,6 +43,10 @@ export interface ChatMessage {
15
43
  /** True while the assistant is still streaming this message. The UI uses
16
44
  * this to render a caret/cursor next to a half-written reply. */
17
45
  isStreaming?: boolean;
46
+ /** Structured payload for non-text messages (approval cards, blocked
47
+ * errors, …). View layers should branch on `metadata.kind` before
48
+ * falling back to `content`. */
49
+ metadata?: ChatMessageMetadata;
18
50
  }
19
51
  export interface ChatAgentSummary {
20
52
  slug: string;
package/dist/index.d.ts CHANGED
@@ -10,4 +10,4 @@
10
10
  export { ChatSession } from './session';
11
11
  export { HttpTransport } from './transport';
12
12
  export type { StreamEvent, ServerStreamChunk, TransportError, } from './transport';
13
- export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatRole, ChatSessionOptions, ChatSessionState, ChatSessionStatus, ChatTheme, } from './entities';
13
+ export type { ChatAgentSummary, ChatEvent, ChatMessage, ChatMessageMetadata, ChatRole, ChatSessionOptions, ChatSessionState, ChatSessionStatus, ChatTheme, } from './entities';
package/dist/session.js CHANGED
@@ -80,6 +80,7 @@ class ChatSession {
80
80
  role: m.role,
81
81
  content: m.content,
82
82
  createdAt: new Date(m.createdAt),
83
+ metadata: m.metadata,
83
84
  });
84
85
  }
85
86
  if (history.status === 'completed') {
@@ -186,6 +187,39 @@ class ChatSession {
186
187
  }
187
188
  if (evt.kind === 'chunk') {
188
189
  sawAnyChunk = true;
190
+ // Approval gate intercepts. The server emits one of these as
191
+ // the final chunk before closing the stream; we mutate the
192
+ // in-flight assistant message with the structured payload so
193
+ // the View can swap the bubble for an Approve/Deny card. The
194
+ // text body falls back to a sensible plain-text version so a
195
+ // legacy widget (no metadata renderer) still shows something
196
+ // readable.
197
+ const chunkType = evt.chunk.type;
198
+ if (chunkType === 'awaiting_approval') {
199
+ const c = evt.chunk;
200
+ assistant.metadata = {
201
+ kind: 'awaiting_approval',
202
+ approvalId: c.approvalId,
203
+ toolName: c.toolName,
204
+ expiresAt: c.expiresAt,
205
+ };
206
+ assistant.content =
207
+ full || `(waiting for approval to run \`${c.toolName}\`)`;
208
+ this.updateMessage(assistant);
209
+ continue;
210
+ }
211
+ if (chunkType === 'tool_blocked') {
212
+ const c = evt.chunk;
213
+ assistant.metadata = {
214
+ kind: 'tool_blocked',
215
+ toolName: c.toolName,
216
+ reason: c.reason,
217
+ };
218
+ assistant.content =
219
+ full || c.reason || `Tool "${c.toolName}" is blocked.`;
220
+ this.updateMessage(assistant);
221
+ continue;
222
+ }
189
223
  const delta = extractDelta(evt.chunk);
190
224
  if (!delta)
191
225
  continue;
@@ -207,7 +241,12 @@ class ChatSession {
207
241
  // than leaving an empty assistant bubble. Most often this means the
208
242
  // backend swallowed an exception inside streamMessage (e.g. the
209
243
  // history query failed or the runner rejected the model).
210
- if (!full) {
244
+ //
245
+ // Exception: when the stream ended on an approval / blocked
246
+ // chunk, the bubble carries that structured payload and is a
247
+ // legitimate terminal state — not a missing reply. Skip the
248
+ // "empty response" guard for those.
249
+ if (!full && !assistant.metadata) {
211
250
  throw new Error(sawAnyChunk
212
251
  ? 'The model returned an empty response.'
213
252
  : 'No response received from the server.');
@@ -77,6 +77,10 @@ export declare class HttpTransport {
77
77
  role: 'user' | 'assistant' | 'system';
78
78
  content: string;
79
79
  createdAt: string;
80
+ /** Structured payload for non-text messages (approval cards,
81
+ * blocked errors). Passed through unchanged from the server's
82
+ * message row so a reload re-renders the same card. */
83
+ metadata?: Record<string, unknown>;
80
84
  }>;
81
85
  } | null>;
82
86
  endConversation(conversationId: string, browserSessionId: string): Promise<void>;
package/dist/transport.js CHANGED
@@ -107,6 +107,7 @@ class HttpTransport {
107
107
  role: m.role,
108
108
  content: m.content,
109
109
  createdAt: m.createdAt,
110
+ metadata: m.metadata,
110
111
  })),
111
112
  };
112
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",