@guuey/agent-client 0.2.2 → 0.3.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/dist/history.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * `credentials: "include"`). Consumed by {@link createWebAdapters}; Portal
11
11
  * has its own copy today and can migrate onto this later.
12
12
  */
13
- import type { JsonValue } from "@silverprotocol/core";
13
+ import type { AgMessage, JsonValue } from "@silverprotocol/core";
14
14
  import type { AgentMessage, HistoryCard, HistoryLoadResult } from "./types";
15
15
  /** One row of `GET /v1/threads/:id/messages`. */
16
16
  export interface ThreadHistoryRow {
@@ -71,4 +71,17 @@ export interface ThreadHistoryFetchOptions {
71
71
  * the chat empty.
72
72
  */
73
73
  export declare function fetchThreadHistory({ baseUrl, threadId, requestInit, includeCards, fetchImpl, }: ThreadHistoryFetchOptions): Promise<HistoryLoadResult>;
74
+ /**
75
+ * The tool name for a `tool-result` block, read off its paired `tool-call`
76
+ * block in the same message (the reducer keeps both in one message's content).
77
+ * Falls back to `"tool"` when the pair is missing.
78
+ */
79
+ export declare function toolNameFor(message: AgMessage, toolCallId: string): string;
80
+ /**
81
+ * Persisted cards, ascending by transcript `seq` (stable; input untouched).
82
+ * These are PRIOR-turn cards — they always precede the live fold, so a
83
+ * block-preserving renderer surfaces them first (e.g. under an "Earlier in
84
+ * this conversation" divider).
85
+ */
86
+ export declare function sortHistoryCards(cards: readonly HistoryCard[]): HistoryCard[];
74
87
  //# sourceMappingURL=history.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5E,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACjC;AAOD;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,SAA6B;CAIjD;AAeD,qEAAqE;AACrE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE,CAOpF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE,CAOhF;AAED,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAoB,EACpB,SAAiB,GAClB,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuBxD"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5E,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACjC;AAOD;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,SAA6B;CAIjD;AAeD,qEAAqE;AACrE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE,CAOpF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE,CAOhF;AAED,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAoB,EACpB,SAAiB,GAClB,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuBxD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAE7E"}
package/dist/history.js CHANGED
@@ -81,3 +81,24 @@ export async function fetchThreadHistory({ baseUrl, threadId, requestInit, inclu
81
81
  ...(includeCards ? { cards: threadHistoryRowsToCards(rows) } : {}),
82
82
  };
83
83
  }
84
+ /**
85
+ * The tool name for a `tool-result` block, read off its paired `tool-call`
86
+ * block in the same message (the reducer keeps both in one message's content).
87
+ * Falls back to `"tool"` when the pair is missing.
88
+ */
89
+ export function toolNameFor(message, toolCallId) {
90
+ for (const b of message.content) {
91
+ if (b.type === "tool-call" && b.toolCallId === toolCallId)
92
+ return b.name;
93
+ }
94
+ return "tool";
95
+ }
96
+ /**
97
+ * Persisted cards, ascending by transcript `seq` (stable; input untouched).
98
+ * These are PRIOR-turn cards — they always precede the live fold, so a
99
+ * block-preserving renderer surfaces them first (e.g. under an "Earlier in
100
+ * this conversation" divider).
101
+ */
102
+ export function sortHistoryCards(cards) {
103
+ return [...cards].sort((a, b) => a.seq - b.seq);
104
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField, parseConsentRequest, parseLinkRequest, type ParsedSseEvent, } from "./sse";
2
2
  export { dismissLinkPrompt } from "./link-prompt";
3
- export { createWebAdapters, localStorageThreadStore, webGenerateId, fetchStreamTransport, AgentResponseError, type CreateWebAdaptersOptions, } from "./web-adapters";
3
+ export { createUiResourceReader, type CreateUiResourceReaderOptions, type ResolvedUiResourceMount, createWebAdapters, localStorageThreadStore, webGenerateId, fetchStreamTransport, AgentResponseError, type CreateWebAdaptersOptions, } from "./web-adapters";
4
4
  export { fetchThreadHistory, threadHistoryRowsToMessages, threadHistoryRowsToCards, HistoryUnauthorizedError, type ThreadHistoryRow, type ThreadHistoryFetchOptions, } from "./history";
5
5
  export { ingestMessageFrame } from "./blocks";
6
- export { asResourcePayload, asUiResource, blockUiResource, cardUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, sortHistoryCards, toolNameFor, toolResultUiResource, type McpUiResourcePayload, } from "./block-ui";
7
- export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, type GguiRenderBootstrap, type GguiRenderDescriptor, } from "./ggui-render";
8
- export { cardCardMount, toolResultCardMount, type CardMount, type CardMountChannel, } from "./card-mount";
6
+ export { sortHistoryCards, toolNameFor } from "./history";
9
7
  export type { AgEvent, AgReduceResult, AgMessage, AgBlock } from "@silverprotocol/core";
10
8
  export type { AgentMessage, HistoryCard, ProfileConsentRequest, ProfileLinkRequest, ThreadIdStore, GenerateId, InvokeRequest, InvokeTransport, AgentInvokeAdapters, AgentInvokeHistoryAdapter, AgentInvokeStatus, HistoryLoadResult, UseAgentInvokeOptions, UseAgentInvokeReturn, } from "./types";
11
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,cAAc,GACpB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,wBAAwB,GAC9B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAG9C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAC;AAIpB,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAItB,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACxF,YAAY,EACV,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,cAAc,GACpB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,wBAAwB,GAC9B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAK9C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAI1D,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACxF,YAAY,EACV,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,13 +1,10 @@
1
1
  export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField, parseConsentRequest, parseLinkRequest, } from "./sse";
2
2
  export { dismissLinkPrompt } from "./link-prompt";
3
- export { createWebAdapters, localStorageThreadStore, webGenerateId, fetchStreamTransport, AgentResponseError, } from "./web-adapters";
3
+ export { createUiResourceReader, createWebAdapters, localStorageThreadStore, webGenerateId, fetchStreamTransport, AgentResponseError, } from "./web-adapters";
4
4
  export { fetchThreadHistory, threadHistoryRowsToMessages, threadHistoryRowsToCards, HistoryUnauthorizedError, } from "./history";
5
5
  export { ingestMessageFrame } from "./blocks";
6
6
  // Pure block-walk / resource-narrowing helpers for a block-preserving renderer
7
7
  // (shared by Studio's `AgentBlocks` and Portal-web's agent chat). React-free.
8
- export { asResourcePayload, asUiResource, blockUiResource, cardUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, sortHistoryCards, toolNameFor, toolResultUiResource, } from "./block-ui";
9
- // The ggui render channel (`uiData.resourceUri` + the `_meta["ai.ggui/render"]`
10
- // bootstrap) and the dispatcher that mounts EITHER channel through the same
11
- // second-origin sandbox path.
12
- export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, } from "./ggui-render";
13
- export { cardCardMount, toolResultCardMount, } from "./card-mount";
8
+ // Transcript labeling/ordering helpers (mount narrowing itself moved to
9
+ // @guuey/mcp-apps-host the SEP-1865 Host role package; import it directly).
10
+ export { sortHistoryCards, toolNameFor } from "./history";
package/dist/sse.d.ts CHANGED
@@ -22,6 +22,11 @@ export declare function parseSseEvents(buffer: string): {
22
22
  * messages APPEND (streaming). The success `result` message carries the SAME
23
23
  * final text, so it REPLACES rather than appends — otherwise the answer renders
24
24
  * twice. The result is also the fallback for result-only turns.
25
+ *
26
+ * Block boundaries become paragraph breaks: a silver `text.start` frame (and,
27
+ * on the bypass arm, each new `assistant` message) opens a new block, so the
28
+ * separator lands exactly there — never per `text.delta`, which must stay a
29
+ * raw append for streaming (#91).
25
30
  */
26
31
  export declare function reduceAssistantText(current: string, data: unknown): string;
27
32
  /**
package/dist/sse.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqBzF;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAM1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAgC1D;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,qBAAqB,GAAG,IAAI,CAO/E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAOzE"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqBzF;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAU1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAmC1D;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,qBAAqB,GAAG,IAAI,CAO/E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAOzE"}
package/dist/sse.js CHANGED
@@ -34,23 +34,49 @@ export function parseSseEvents(buffer) {
34
34
  }
35
35
  return { events, rest };
36
36
  }
37
+ function frameType(data) {
38
+ if (typeof data !== "object" || data === null)
39
+ return undefined;
40
+ return data.type;
41
+ }
37
42
  function isResultMessage(data) {
38
- return (typeof data === "object" &&
39
- data !== null &&
40
- data.type === "result");
43
+ return frameType(data) === "result";
44
+ }
45
+ /**
46
+ * Join point where a NEW text block begins: distinct blocks are distinct
47
+ * paragraphs on the flat text surface (guuey#98 — raw concatenation rendered
48
+ * "…instead.Here's your packing…"). Idempotent so an empty block between two
49
+ * boundaries never stacks separators.
50
+ */
51
+ function withBlockBreak(current) {
52
+ if (!current || current.endsWith("\n\n"))
53
+ return current;
54
+ return current + "\n\n";
41
55
  }
42
56
  /**
43
57
  * Fold one `message` SSE payload into the running assistant text. `assistant`
44
58
  * messages APPEND (streaming). The success `result` message carries the SAME
45
59
  * final text, so it REPLACES rather than appends — otherwise the answer renders
46
60
  * twice. The result is also the fallback for result-only turns.
61
+ *
62
+ * Block boundaries become paragraph breaks: a silver `text.start` frame (and,
63
+ * on the bypass arm, each new `assistant` message) opens a new block, so the
64
+ * separator lands exactly there — never per `text.delta`, which must stay a
65
+ * raw append for streaming (#91).
47
66
  */
48
67
  export function reduceAssistantText(current, data) {
49
68
  if (isResultMessage(data)) {
50
69
  const result = extractAssistantText(data);
51
70
  return result || current;
52
71
  }
53
- return current + extractAssistantText(data);
72
+ if (frameType(data) === "text.start")
73
+ return withBlockBreak(current);
74
+ const text = extractAssistantText(data);
75
+ if (!text)
76
+ return current;
77
+ if (frameType(data) === "assistant")
78
+ return withBlockBreak(current) + text;
79
+ return current + text;
54
80
  }
55
81
  /**
56
82
  * Best-effort assistant text from one `message` payload — BOTH protocols:
@@ -79,13 +105,16 @@ export function extractAssistantText(data) {
79
105
  if (typeof message === "object" && message !== null && "content" in message) {
80
106
  const content = message.content;
81
107
  if (Array.isArray(content)) {
108
+ // Distinct text blocks are distinct paragraphs (guuey#98) — same
109
+ // block-boundary contract as the silver arm's text.start handling.
82
110
  return content
83
111
  .filter((b) => typeof b === "object" &&
84
112
  b !== null &&
85
113
  b.type === "text" &&
86
114
  typeof b.text === "string")
87
115
  .map((b) => b.text)
88
- .join("");
116
+ .filter((t) => t.length > 0)
117
+ .join("\n\n");
89
118
  }
90
119
  }
91
120
  return "";
@@ -120,4 +120,52 @@ export interface CreateWebAdaptersOptions {
120
120
  * cookie-only, history-less bundle.
121
121
  */
122
122
  export declare function createWebAdapters(opts?: CreateWebAdaptersOptions): AgentInvokeAdapters;
123
+ /** Options for {@link createUiResourceReader} — same credential surface as the history adapter. */
124
+ export interface CreateUiResourceReaderOptions {
125
+ /** The guuey public API base (`…/v1`). */
126
+ apiBaseUrl: string;
127
+ /** The thread whose persisted locators this reader may resolve. */
128
+ threadId: string;
129
+ /** Signed-in bearer — wins over the guest secret (same rule as the transport). */
130
+ getAccessToken?: (opts?: {
131
+ forceRefresh?: boolean;
132
+ }) => Promise<string | null>;
133
+ /** Caller-owned anonymous guest secret (widget / guest chat). */
134
+ guestSecret?: string | null;
135
+ /** Injectable for tests. */
136
+ fetchImpl?: typeof fetch;
137
+ }
138
+ /**
139
+ * Build a `@guuey/mcp-apps-host` `UiResourceReader` over guuey's
140
+ * authenticated resources/read proxy (guuey#122 Gap 1:
141
+ * `GET /v1/threads/:threadId/ui-resource?uri=…`).
142
+ *
143
+ * The proxy owns EVERYTHING trust-shaped: caller identity (the same three
144
+ * families as the history read), thread ownership, the locator-to-thread
145
+ * scope guard, and the per-user federation mint. This reader only carries
146
+ * the surface's existing credential and maps EVERY non-OK — 401/403/404/502
147
+ * alike — to `undefined`: deny is byte-identical to a miss, and a miss
148
+ * renders the host's placeholder, never an error surface.
149
+ *
150
+ * Channel resolution is the phase-1 heuristic: a `ui://ggui/…` uri mounts in
151
+ * the ggui-CSP sandbox page (`channel: "ggui"`), anything else in the
152
+ * self-only page (`channel: "inline"`). Phase 2 (per-resource declared-CSP
153
+ * construction from the response `_meta.ui.csp`) retires the heuristic —
154
+ * conformance map, retirement step 3.
155
+ */
156
+ /**
157
+ * The mount the reader resolves — structurally assignable to
158
+ * `@guuey/mcp-apps-host`'s `ViewMount` mountable arms (typed inline so this
159
+ * package carries no dependency on the host package; hosts that consume
160
+ * both see one structural type).
161
+ */
162
+ export interface ResolvedUiResourceMount {
163
+ channel: "inline" | "ggui";
164
+ resource: {
165
+ uri: string;
166
+ mimeType?: string;
167
+ text: string;
168
+ };
169
+ }
170
+ export declare function createUiResourceReader(options: CreateUiResourceReaderOptions): (resourceUri: string) => Promise<ResolvedUiResourceMount | undefined>;
123
171
  //# sourceMappingURL=web-adapters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,aAAa,EACd,MAAM,SAAS,CAAC;AAGjB;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,EAAE,aAiBrC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAwCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuB,oBAAoB,CACzC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,cAAc,CAAC,MAAM,CAAC,CA4CxB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE,wBAA6B,GAClC,mBAAmB,CAwErB"}
1
+ {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,aAAa,EACd,MAAM,SAAS,CAAC;AAGjB;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,EAAE,aAiBrC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAwCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuB,oBAAoB,CACzC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,cAAc,CAAC,MAAM,CAAC,CA4CxB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE,wBAA6B,GAClC,mBAAmB,CAwErB;AAED,mGAAmG;AACnG,MAAM,WAAW,6BAA6B;IAC5C,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,6BAA6B,GACrC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAkDvE"}
@@ -227,3 +227,60 @@ export function createWebAdapters(opts = {}) {
227
227
  }
228
228
  return adapters;
229
229
  }
230
+ export function createUiResourceReader(options) {
231
+ const fetchImpl = options.fetchImpl ?? fetch;
232
+ return async (resourceUri) => {
233
+ const headers = {};
234
+ const token = options.getAccessToken ? await options.getAccessToken() : null;
235
+ const guest = sendableGuestSecret(options.guestSecret);
236
+ if (token) {
237
+ headers["authorization"] = `Bearer ${token}`;
238
+ }
239
+ else if (guest) {
240
+ headers[GUEST_HEADER] = guest;
241
+ }
242
+ const requestUrl = `${options.apiBaseUrl}/threads/${encodeURIComponent(options.threadId)}/ui-resource?uri=${encodeURIComponent(resourceUri)}`;
243
+ let res;
244
+ try {
245
+ res = await fetchImpl(requestUrl, { headers });
246
+ }
247
+ catch {
248
+ return undefined; // transport failure == miss == placeholder
249
+ }
250
+ // One forceRefresh retry on 401 with a bearer in play — the same
251
+ // expired-but-refreshable recovery the history adapter performs;
252
+ // without it a stale token degrades to a permanent placeholder.
253
+ if (res.status === 401 && options.getAccessToken) {
254
+ const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
255
+ if (fresh) {
256
+ try {
257
+ res = await fetchImpl(requestUrl, {
258
+ headers: { ...headers, authorization: `Bearer ${fresh}` },
259
+ });
260
+ }
261
+ catch {
262
+ return undefined;
263
+ }
264
+ }
265
+ }
266
+ if (!res.ok)
267
+ return undefined;
268
+ let body;
269
+ try {
270
+ body = (await res.json());
271
+ }
272
+ catch {
273
+ return undefined;
274
+ }
275
+ if (typeof body.uri !== "string" || typeof body.text !== "string")
276
+ return undefined;
277
+ return {
278
+ channel: resourceUri.startsWith("ui://ggui/") ? "ggui" : "inline",
279
+ resource: {
280
+ uri: body.uri,
281
+ ...(typeof body.mimeType === "string" ? { mimeType: body.mimeType } : {}),
282
+ text: body.text,
283
+ },
284
+ };
285
+ };
286
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/agent-client",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Client SDK for Guuey's agent runtime: the `useAgentInvoke` React hook + pure SSE helpers that speak the /agent/invoke streaming contract, plus the paginated thread-history read plane. Host adapters (storage / id / transport) are injected, so it runs on web (Next) and React Native alike.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/history.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * `credentials: "include"`). Consumed by {@link createWebAdapters}; Portal
11
11
  * has its own copy today and can migrate onto this later.
12
12
  */
13
- import type { JsonValue } from "@silverprotocol/core";
13
+ import type { AgMessage, JsonValue } from "@silverprotocol/core";
14
14
  import type { AgentMessage, HistoryCard, HistoryLoadResult } from "./types";
15
15
 
16
16
  /** One row of `GET /v1/threads/:id/messages`. */
@@ -141,3 +141,25 @@ export async function fetchThreadHistory({
141
141
  ...(includeCards ? { cards: threadHistoryRowsToCards(rows) } : {}),
142
142
  };
143
143
  }
144
+
145
+ /**
146
+ * The tool name for a `tool-result` block, read off its paired `tool-call`
147
+ * block in the same message (the reducer keeps both in one message's content).
148
+ * Falls back to `"tool"` when the pair is missing.
149
+ */
150
+ export function toolNameFor(message: AgMessage, toolCallId: string): string {
151
+ for (const b of message.content) {
152
+ if (b.type === "tool-call" && b.toolCallId === toolCallId) return b.name;
153
+ }
154
+ return "tool";
155
+ }
156
+
157
+ /**
158
+ * Persisted cards, ascending by transcript `seq` (stable; input untouched).
159
+ * These are PRIOR-turn cards — they always precede the live fold, so a
160
+ * block-preserving renderer surfaces them first (e.g. under an "Earlier in
161
+ * this conversation" divider).
162
+ */
163
+ export function sortHistoryCards(cards: readonly HistoryCard[]): HistoryCard[] {
164
+ return [...cards].sort((a, b) => a.seq - b.seq);
165
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,9 @@ export {
9
9
  } from "./sse";
10
10
  export { dismissLinkPrompt } from "./link-prompt";
11
11
  export {
12
+ createUiResourceReader,
13
+ type CreateUiResourceReaderOptions,
14
+ type ResolvedUiResourceMount,
12
15
  createWebAdapters,
13
16
  localStorageThreadStore,
14
17
  webGenerateId,
@@ -27,39 +30,9 @@ export {
27
30
  export { ingestMessageFrame } from "./blocks";
28
31
  // Pure block-walk / resource-narrowing helpers for a block-preserving renderer
29
32
  // (shared by Studio's `AgentBlocks` and Portal-web's agent chat). React-free.
30
- export {
31
- asResourcePayload,
32
- asUiResource,
33
- blockUiResource,
34
- cardUiResource,
35
- isJsonObject,
36
- resourceHtml,
37
- scanProviderRawForUiResource,
38
- sortHistoryCards,
39
- toolNameFor,
40
- toolResultUiResource,
41
- type McpUiResourcePayload,
42
- } from "./block-ui";
43
- // The ggui render channel (`uiData.resourceUri` + the `_meta["ai.ggui/render"]`
44
- // bootstrap) and the dispatcher that mounts EITHER channel through the same
45
- // second-origin sandbox path.
46
- export {
47
- asGguiRender,
48
- asGguiRenderBootstrap,
49
- blockGguiRender,
50
- gguiRenderResource,
51
- gguiShellHtml,
52
- toolResultGguiRender,
53
- GGUI_RENDER_META_KEY,
54
- type GguiRenderBootstrap,
55
- type GguiRenderDescriptor,
56
- } from "./ggui-render";
57
- export {
58
- cardCardMount,
59
- toolResultCardMount,
60
- type CardMount,
61
- type CardMountChannel,
62
- } from "./card-mount";
33
+ // Transcript labeling/ordering helpers (mount narrowing itself moved to
34
+ // @guuey/mcp-apps-host — the SEP-1865 Host role package; import it directly).
35
+ export { sortHistoryCards, toolNameFor } from "./history";
63
36
  // Re-export the AgJSON types the block-preserving transcript surfaces, so
64
37
  // consumers can name `reduceResult` / block types without a direct
65
38
  // `@silverprotocol/core` import.
package/src/sse.ts CHANGED
@@ -39,12 +39,24 @@ export function parseSseEvents(buffer: string): { events: ParsedSseEvent[]; rest
39
39
  return { events, rest };
40
40
  }
41
41
 
42
+ function frameType(data: unknown): unknown {
43
+ if (typeof data !== "object" || data === null) return undefined;
44
+ return (data as { type?: unknown }).type;
45
+ }
46
+
42
47
  function isResultMessage(data: unknown): boolean {
43
- return (
44
- typeof data === "object" &&
45
- data !== null &&
46
- (data as { type?: unknown }).type === "result"
47
- );
48
+ return frameType(data) === "result";
49
+ }
50
+
51
+ /**
52
+ * Join point where a NEW text block begins: distinct blocks are distinct
53
+ * paragraphs on the flat text surface (guuey#98 — raw concatenation rendered
54
+ * "…instead.Here's your packing…"). Idempotent so an empty block between two
55
+ * boundaries never stacks separators.
56
+ */
57
+ function withBlockBreak(current: string): string {
58
+ if (!current || current.endsWith("\n\n")) return current;
59
+ return current + "\n\n";
48
60
  }
49
61
 
50
62
  /**
@@ -52,13 +64,22 @@ function isResultMessage(data: unknown): boolean {
52
64
  * messages APPEND (streaming). The success `result` message carries the SAME
53
65
  * final text, so it REPLACES rather than appends — otherwise the answer renders
54
66
  * twice. The result is also the fallback for result-only turns.
67
+ *
68
+ * Block boundaries become paragraph breaks: a silver `text.start` frame (and,
69
+ * on the bypass arm, each new `assistant` message) opens a new block, so the
70
+ * separator lands exactly there — never per `text.delta`, which must stay a
71
+ * raw append for streaming (#91).
55
72
  */
56
73
  export function reduceAssistantText(current: string, data: unknown): string {
57
74
  if (isResultMessage(data)) {
58
75
  const result = extractAssistantText(data);
59
76
  return result || current;
60
77
  }
61
- return current + extractAssistantText(data);
78
+ if (frameType(data) === "text.start") return withBlockBreak(current);
79
+ const text = extractAssistantText(data);
80
+ if (!text) return current;
81
+ if (frameType(data) === "assistant") return withBlockBreak(current) + text;
82
+ return current + text;
62
83
  }
63
84
 
64
85
  /**
@@ -87,6 +108,8 @@ export function extractAssistantText(data: unknown): string {
87
108
  if (typeof message === "object" && message !== null && "content" in message) {
88
109
  const content = (message as { content?: unknown }).content;
89
110
  if (Array.isArray(content)) {
111
+ // Distinct text blocks are distinct paragraphs (guuey#98) — same
112
+ // block-boundary contract as the silver arm's text.start handling.
90
113
  return content
91
114
  .filter(
92
115
  (b): b is { type: "text"; text: string } =>
@@ -96,7 +119,8 @@ export function extractAssistantText(data: unknown): string {
96
119
  typeof (b as { text?: unknown }).text === "string",
97
120
  )
98
121
  .map((b) => b.text)
99
- .join("");
122
+ .filter((t) => t.length > 0)
123
+ .join("\n\n");
100
124
  }
101
125
  }
102
126
  return "";
@@ -317,3 +317,100 @@ export function createWebAdapters(
317
317
 
318
318
  return adapters;
319
319
  }
320
+
321
+ /** Options for {@link createUiResourceReader} — same credential surface as the history adapter. */
322
+ export interface CreateUiResourceReaderOptions {
323
+ /** The guuey public API base (`…/v1`). */
324
+ apiBaseUrl: string;
325
+ /** The thread whose persisted locators this reader may resolve. */
326
+ threadId: string;
327
+ /** Signed-in bearer — wins over the guest secret (same rule as the transport). */
328
+ getAccessToken?: (opts?: { forceRefresh?: boolean }) => Promise<string | null>;
329
+ /** Caller-owned anonymous guest secret (widget / guest chat). */
330
+ guestSecret?: string | null;
331
+ /** Injectable for tests. */
332
+ fetchImpl?: typeof fetch;
333
+ }
334
+
335
+ /**
336
+ * Build a `@guuey/mcp-apps-host` `UiResourceReader` over guuey's
337
+ * authenticated resources/read proxy (guuey#122 Gap 1:
338
+ * `GET /v1/threads/:threadId/ui-resource?uri=…`).
339
+ *
340
+ * The proxy owns EVERYTHING trust-shaped: caller identity (the same three
341
+ * families as the history read), thread ownership, the locator-to-thread
342
+ * scope guard, and the per-user federation mint. This reader only carries
343
+ * the surface's existing credential and maps EVERY non-OK — 401/403/404/502
344
+ * alike — to `undefined`: deny is byte-identical to a miss, and a miss
345
+ * renders the host's placeholder, never an error surface.
346
+ *
347
+ * Channel resolution is the phase-1 heuristic: a `ui://ggui/…` uri mounts in
348
+ * the ggui-CSP sandbox page (`channel: "ggui"`), anything else in the
349
+ * self-only page (`channel: "inline"`). Phase 2 (per-resource declared-CSP
350
+ * construction from the response `_meta.ui.csp`) retires the heuristic —
351
+ * conformance map, retirement step 3.
352
+ */
353
+ /**
354
+ * The mount the reader resolves — structurally assignable to
355
+ * `@guuey/mcp-apps-host`'s `ViewMount` mountable arms (typed inline so this
356
+ * package carries no dependency on the host package; hosts that consume
357
+ * both see one structural type).
358
+ */
359
+ export interface ResolvedUiResourceMount {
360
+ channel: "inline" | "ggui";
361
+ resource: { uri: string; mimeType?: string; text: string };
362
+ }
363
+
364
+ export function createUiResourceReader(
365
+ options: CreateUiResourceReaderOptions,
366
+ ): (resourceUri: string) => Promise<ResolvedUiResourceMount | undefined> {
367
+ const fetchImpl = options.fetchImpl ?? fetch;
368
+ return async (resourceUri) => {
369
+ const headers: Record<string, string> = {};
370
+ const token = options.getAccessToken ? await options.getAccessToken() : null;
371
+ const guest = sendableGuestSecret(options.guestSecret);
372
+ if (token) {
373
+ headers["authorization"] = `Bearer ${token}`;
374
+ } else if (guest) {
375
+ headers[GUEST_HEADER] = guest;
376
+ }
377
+ const requestUrl = `${options.apiBaseUrl}/threads/${encodeURIComponent(options.threadId)}/ui-resource?uri=${encodeURIComponent(resourceUri)}`;
378
+ let res: Response;
379
+ try {
380
+ res = await fetchImpl(requestUrl, { headers });
381
+ } catch {
382
+ return undefined; // transport failure == miss == placeholder
383
+ }
384
+ // One forceRefresh retry on 401 with a bearer in play — the same
385
+ // expired-but-refreshable recovery the history adapter performs;
386
+ // without it a stale token degrades to a permanent placeholder.
387
+ if (res.status === 401 && options.getAccessToken) {
388
+ const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
389
+ if (fresh) {
390
+ try {
391
+ res = await fetchImpl(requestUrl, {
392
+ headers: { ...headers, authorization: `Bearer ${fresh}` },
393
+ });
394
+ } catch {
395
+ return undefined;
396
+ }
397
+ }
398
+ }
399
+ if (!res.ok) return undefined;
400
+ let body: { uri?: unknown; mimeType?: unknown; text?: unknown };
401
+ try {
402
+ body = (await res.json()) as typeof body;
403
+ } catch {
404
+ return undefined;
405
+ }
406
+ if (typeof body.uri !== "string" || typeof body.text !== "string") return undefined;
407
+ return {
408
+ channel: resourceUri.startsWith("ui://ggui/") ? "ggui" : "inline",
409
+ resource: {
410
+ uri: body.uri,
411
+ ...(typeof body.mimeType === "string" ? { mimeType: body.mimeType } : {}),
412
+ text: body.text,
413
+ },
414
+ };
415
+ };
416
+ }