@popcomputer/structured-chat 0.2.0-rc.0 → 0.2.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.
@@ -1,4 +1,5 @@
1
1
  import { type ComponentType, type FC } from "react";
2
+ import type { StructuredChatDebugSnapshot } from "../core/debug.js";
2
3
  import type { ViewData, ViewDefinitionContract } from "../core/view.js";
3
4
  import { type StructuredChatSessionReference, type StructuredChatAssistantMessage } from "../core/protocol.js";
4
5
  /** Runtime status supplied by assistant-ui to a data-part renderer. */
@@ -52,6 +53,12 @@ export type AssistantChatFetch = (input: string, init: RequestInit) => Promise<R
52
53
  export interface AssistantChatModelAdapterOptions {
53
54
  readonly endpoint: string;
54
55
  readonly fetch?: AssistantChatFetch;
56
+ /**
57
+ * Select the explicit debug response contract and receive its safe state
58
+ * projection after every successful turn. Observer failures are ignored so
59
+ * they cannot change the outcome of an already-persisted chat turn.
60
+ */
61
+ readonly onDebugSnapshot?: (snapshot: StructuredChatDebugSnapshot) => void | Promise<void>;
55
62
  }
56
63
  /** Minimum assistant message shape consumed by the browser adapter. */
57
64
  export interface AssistantChatThreadMessage {
@@ -88,8 +95,8 @@ export interface AssistantChatModelAdapter {
88
95
  /**
89
96
  * Adapt assistant-ui to one server-owned structured chat endpoint.
90
97
  *
91
- * Only the latest text and opaque prior revision cross the browser boundary.
92
- * Tools, instructions, history, answer state, and stage position stay server
93
- * owned.
98
+ * By default, only the latest text and opaque prior revision cross the browser
99
+ * boundary. Supplying `onDebugSnapshot` explicitly selects the separate debug
100
+ * response contract and receives its safe answer-and-stage projection.
94
101
  */
95
102
  export declare const makeAssistantChatModelAdapter: (options: AssistantChatModelAdapterOptions) => AssistantChatModelAdapter;
@@ -39,6 +39,15 @@ export const makeAssistantView = (view, config) => {
39
39
  };
40
40
  /** assistant-ui metadata key carrying the latest opaque session reference. */
41
41
  export const assistantChatSessionMetadataKey = "popcomputerStructuredChatSession";
42
+ const notifyDebugSnapshot = (callback, snapshot) => {
43
+ try {
44
+ const notified = callback(snapshot);
45
+ void Promise.resolve(notified).catch(() => undefined);
46
+ }
47
+ catch {
48
+ // Debug observers are deliberately isolated from the persisted turn.
49
+ }
50
+ };
42
51
  const readMessageText = (message) => message.content
43
52
  .flatMap((part) => part.type === "text" && "text" in part ? [part.text] : [])
44
53
  .join("\n")
@@ -72,13 +81,14 @@ const readTurnRequest = (messages) => {
72
81
  /**
73
82
  * Adapt assistant-ui to one server-owned structured chat endpoint.
74
83
  *
75
- * Only the latest text and opaque prior revision cross the browser boundary.
76
- * Tools, instructions, history, answer state, and stage position stay server
77
- * owned.
84
+ * By default, only the latest text and opaque prior revision cross the browser
85
+ * boundary. Supplying `onDebugSnapshot` explicitly selects the separate debug
86
+ * response contract and receives its safe answer-and-stage projection.
78
87
  */
79
88
  export const makeAssistantChatModelAdapter = (options) => {
80
89
  const fetch_ = options.fetch ??
81
90
  ((input, init) => globalThis.fetch(input, init));
91
+ const onDebugSnapshot = options.onDebugSnapshot;
82
92
  return {
83
93
  run: async ({ messages, abortSignal }) => {
84
94
  const request = readTurnRequest(messages);
@@ -102,17 +112,30 @@ export const makeAssistantChatModelAdapter = (options) => {
102
112
  catch {
103
113
  throw new Error("Structured chat returned an invalid response");
104
114
  }
105
- const decoded = Schema.decodeUnknownExit(StructuredChatTurnResponseSchema)(body, { onExcessProperty: "error" });
106
- if (Exit.isFailure(decoded)) {
107
- throw new Error("Structured chat returned an invalid response");
115
+ let value;
116
+ if (onDebugSnapshot === undefined) {
117
+ const decoded = Schema.decodeUnknownExit(StructuredChatTurnResponseSchema)(body, { onExcessProperty: "error" });
118
+ if (Exit.isFailure(decoded)) {
119
+ throw new Error("Structured chat returned an invalid response");
120
+ }
121
+ value = decoded.value;
122
+ }
123
+ else {
124
+ const { StructuredChatDebugTurnResponseSchema } = await import("../core/debug-protocol.js");
125
+ const decoded = Schema.decodeUnknownExit(StructuredChatDebugTurnResponseSchema)(body, { onExcessProperty: "error" });
126
+ if (Exit.isFailure(decoded)) {
127
+ throw new Error("Structured chat returned an invalid response");
128
+ }
129
+ notifyDebugSnapshot(onDebugSnapshot, decoded.value.debug);
130
+ value = decoded.value;
108
131
  }
109
132
  return {
110
- content: decoded.value.message.content,
133
+ content: value.message.content,
111
134
  metadata: {
112
- custom: decoded.value.session === undefined
135
+ custom: value.session === undefined
113
136
  ? {}
114
137
  : {
115
- [assistantChatSessionMetadataKey]: decoded.value.session,
138
+ [assistantChatSessionMetadataKey]: value.session,
116
139
  },
117
140
  },
118
141
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popcomputer/structured-chat",
3
- "version": "0.2.0-rc.0",
3
+ "version": "0.2.0",
4
4
  "description": "Schema-defined structured chats with typed tools, stages, and UI views",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.1",
@@ -23,6 +23,10 @@
23
23
  "./assistant-ui": {
24
24
  "types": "./dist/integrations/assistant-ui.d.ts",
25
25
  "import": "./dist/integrations/assistant-ui.js"
26
+ },
27
+ "./assistant-ui/debug": {
28
+ "types": "./dist/integrations/assistant-ui-debug.d.ts",
29
+ "import": "./dist/integrations/assistant-ui-debug.js"
26
30
  }
27
31
  },
28
32
  "files": [