@guuey/agent-client 0.2.3 → 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 +14 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +21 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -7
- package/dist/sse.d.ts +5 -0
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +34 -5
- package/dist/web-adapters.d.ts +48 -0
- package/dist/web-adapters.d.ts.map +1 -1
- package/dist/web-adapters.js +57 -0
- package/package.json +1 -2
- package/src/history.ts +23 -1
- package/src/index.ts +6 -34
- package/src/sse.ts +31 -7
- package/src/web-adapters.ts +97 -0
- package/dist/block-ui.d.ts +0 -117
- package/dist/block-ui.d.ts.map +0 -1
- package/dist/block-ui.js +0 -183
- package/dist/card-mount.d.ts +0 -69
- package/dist/card-mount.d.ts.map +0 -1
- package/dist/card-mount.js +0 -83
- package/dist/ggui-render.d.ts +0 -120
- package/dist/ggui-render.d.ts.map +0 -1
- package/dist/ggui-render.js +0 -85
- package/src/block-ui.ts +0 -212
- package/src/card-mount.ts +0 -101
- package/src/ggui-render.ts +0 -168
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
|
package/dist/history.d.ts.map
CHANGED
|
@@ -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;
|
|
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 {
|
|
7
|
-
export { asGguiRender, asGguiRenderBootstrap, blockGguiRender, gguiRenderResource, gguiShellHtml, toolResultGguiRender, GGUI_RENDER_META_KEY, type GguiRenderBootstrap, type GguiRenderDescriptor, type GguiShellHtmlOptions, } 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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
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;
|
|
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 (
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
116
|
+
.filter((t) => t.length > 0)
|
|
117
|
+
.join("\n\n");
|
|
89
118
|
}
|
|
90
119
|
}
|
|
91
120
|
return "";
|
package/dist/web-adapters.d.ts
CHANGED
|
@@ -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"}
|
package/dist/web-adapters.js
CHANGED
|
@@ -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.
|
|
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",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ggui-ai/protocol": "0.6.3",
|
|
32
31
|
"@silverprotocol/core": "0.4.1"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
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,40 +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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
type GguiShellHtmlOptions,
|
|
57
|
-
} from "./ggui-render";
|
|
58
|
-
export {
|
|
59
|
-
cardCardMount,
|
|
60
|
-
toolResultCardMount,
|
|
61
|
-
type CardMount,
|
|
62
|
-
type CardMountChannel,
|
|
63
|
-
} 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";
|
|
64
36
|
// Re-export the AgJSON types the block-preserving transcript surfaces, so
|
|
65
37
|
// consumers can name `reduceResult` / block types without a direct
|
|
66
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
122
|
+
.filter((t) => t.length > 0)
|
|
123
|
+
.join("\n\n");
|
|
100
124
|
}
|
|
101
125
|
}
|
|
102
126
|
return "";
|
package/src/web-adapters.ts
CHANGED
|
@@ -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
|
+
}
|