@guuey/agent-client 0.2.3 → 0.3.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/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 +31 -0
- package/dist/web-adapters.d.ts.map +1 -1
- package/dist/web-adapters.js +83 -0
- package/package.json +3 -3
- package/src/history.ts +23 -1
- package/src/index.ts +5 -34
- package/src/sse.ts +31 -7
- package/src/web-adapters.ts +89 -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, 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,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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* functions — never at module load — so this file is import-safe under SSR
|
|
7
7
|
* (the functions guard on `typeof window`).
|
|
8
8
|
*/
|
|
9
|
+
import { type ResolvedViewMount } from "@guuey/mcp-apps-host";
|
|
9
10
|
import type { AgentInvokeAdapters, InvokeRequest, ThreadIdStore } from "./types";
|
|
10
11
|
/**
|
|
11
12
|
* Thrown when the pod returns a non-2xx status on `/agent/invoke` (before any
|
|
@@ -120,4 +121,34 @@ export interface CreateWebAdaptersOptions {
|
|
|
120
121
|
* cookie-only, history-less bundle.
|
|
121
122
|
*/
|
|
122
123
|
export declare function createWebAdapters(opts?: CreateWebAdaptersOptions): AgentInvokeAdapters;
|
|
124
|
+
/** Options for {@link createUiResourceReader} — same credential surface as the history adapter. */
|
|
125
|
+
export interface CreateUiResourceReaderOptions {
|
|
126
|
+
/** The guuey public API base (`…/v1`). */
|
|
127
|
+
apiBaseUrl: string;
|
|
128
|
+
/** The thread whose persisted locators this reader may resolve. */
|
|
129
|
+
threadId: string;
|
|
130
|
+
/** Signed-in bearer — wins over the guest secret (same rule as the transport). */
|
|
131
|
+
getAccessToken?: (opts?: {
|
|
132
|
+
forceRefresh?: boolean;
|
|
133
|
+
}) => Promise<string | null>;
|
|
134
|
+
/** Caller-owned anonymous guest secret (widget / guest chat). */
|
|
135
|
+
guestSecret?: string | null;
|
|
136
|
+
/** Injectable for tests. */
|
|
137
|
+
fetchImpl?: typeof fetch;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build a `UiResourceReader` over guuey's authenticated resources/read proxy
|
|
141
|
+
* (guuey#122 Gap 1: `GET /v1/threads/:threadId/ui-resource?uri=…`).
|
|
142
|
+
*
|
|
143
|
+
* This is `@guuey/mcp-apps-host`'s `createMcpUiResourceReader` assembly over
|
|
144
|
+
* a guuey-platform transport (guuey#127) — channel resolution and payload
|
|
145
|
+
* narrowing live in the host package; only the transport is guuey-shaped.
|
|
146
|
+
* The proxy owns EVERYTHING trust-shaped: caller identity (the same three
|
|
147
|
+
* families as the history read), thread ownership, the locator-to-thread
|
|
148
|
+
* scope guard, and the per-user federation mint. This transport only carries
|
|
149
|
+
* the surface's existing credential and maps EVERY non-OK — 401/403/404/502
|
|
150
|
+
* alike — to `undefined`: deny is byte-identical to a miss, and a miss
|
|
151
|
+
* renders the host's placeholder, never an error surface.
|
|
152
|
+
*/
|
|
153
|
+
export declare function createUiResourceReader(options: CreateUiResourceReaderOptions): (resourceUri: string) => Promise<ResolvedViewMount | undefined>;
|
|
123
154
|
//# 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,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,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;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,6BAA6B,GACrC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAoDjE"}
|
package/dist/web-adapters.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web (browser / Next.js) host adapters for {@link useAgentInvoke}.
|
|
3
|
+
*
|
|
4
|
+
* Studio builds its bundle via {@link createWebAdapters}. The implementations
|
|
5
|
+
* touch `window.localStorage`, `crypto`, and `fetch` only inside their
|
|
6
|
+
* functions — never at module load — so this file is import-safe under SSR
|
|
7
|
+
* (the functions guard on `typeof window`).
|
|
8
|
+
*/
|
|
9
|
+
import { createMcpUiResourceReader, } from "@guuey/mcp-apps-host";
|
|
1
10
|
import { fetchThreadHistory, HistoryUnauthorizedError } from "./history";
|
|
2
11
|
/**
|
|
3
12
|
* Thrown when the pod returns a non-2xx status on `/agent/invoke` (before any
|
|
@@ -227,3 +236,77 @@ export function createWebAdapters(opts = {}) {
|
|
|
227
236
|
}
|
|
228
237
|
return adapters;
|
|
229
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Build a `UiResourceReader` over guuey's authenticated resources/read proxy
|
|
241
|
+
* (guuey#122 Gap 1: `GET /v1/threads/:threadId/ui-resource?uri=…`).
|
|
242
|
+
*
|
|
243
|
+
* This is `@guuey/mcp-apps-host`'s `createMcpUiResourceReader` assembly over
|
|
244
|
+
* a guuey-platform transport (guuey#127) — channel resolution and payload
|
|
245
|
+
* narrowing live in the host package; only the transport is guuey-shaped.
|
|
246
|
+
* The proxy owns EVERYTHING trust-shaped: caller identity (the same three
|
|
247
|
+
* families as the history read), thread ownership, the locator-to-thread
|
|
248
|
+
* scope guard, and the per-user federation mint. This transport only carries
|
|
249
|
+
* the surface's existing credential and maps EVERY non-OK — 401/403/404/502
|
|
250
|
+
* alike — to `undefined`: deny is byte-identical to a miss, and a miss
|
|
251
|
+
* renders the host's placeholder, never an error surface.
|
|
252
|
+
*/
|
|
253
|
+
export function createUiResourceReader(options) {
|
|
254
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
255
|
+
const readResource = async (resourceUri) => {
|
|
256
|
+
const headers = {};
|
|
257
|
+
const token = options.getAccessToken ? await options.getAccessToken() : null;
|
|
258
|
+
const guest = sendableGuestSecret(options.guestSecret);
|
|
259
|
+
if (token) {
|
|
260
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
261
|
+
}
|
|
262
|
+
else if (guest) {
|
|
263
|
+
headers[GUEST_HEADER] = guest;
|
|
264
|
+
}
|
|
265
|
+
const requestUrl = `${options.apiBaseUrl}/threads/${encodeURIComponent(options.threadId)}/ui-resource?uri=${encodeURIComponent(resourceUri)}`;
|
|
266
|
+
let res;
|
|
267
|
+
try {
|
|
268
|
+
res = await fetchImpl(requestUrl, { headers });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return undefined; // transport failure == miss == placeholder
|
|
272
|
+
}
|
|
273
|
+
// One forceRefresh retry on 401 with a bearer in play — the same
|
|
274
|
+
// expired-but-refreshable recovery the history adapter performs;
|
|
275
|
+
// without it a stale token degrades to a permanent placeholder.
|
|
276
|
+
if (res.status === 401 && options.getAccessToken) {
|
|
277
|
+
const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
|
|
278
|
+
if (fresh) {
|
|
279
|
+
try {
|
|
280
|
+
res = await fetchImpl(requestUrl, {
|
|
281
|
+
headers: { ...headers, authorization: `Bearer ${fresh}` },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!res.ok)
|
|
290
|
+
return undefined;
|
|
291
|
+
let body;
|
|
292
|
+
try {
|
|
293
|
+
body = (await res.json());
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
// The proxy passes the blob arm through (a blob-only resource is not
|
|
299
|
+
// silently a miss — its route contract); mirror that here.
|
|
300
|
+
if (typeof body.uri !== "string")
|
|
301
|
+
return undefined;
|
|
302
|
+
if (typeof body.text !== "string" && typeof body.blob !== "string")
|
|
303
|
+
return undefined;
|
|
304
|
+
return {
|
|
305
|
+
uri: body.uri,
|
|
306
|
+
...(typeof body.mimeType === "string" ? { mimeType: body.mimeType } : {}),
|
|
307
|
+
...(typeof body.text === "string" ? { text: body.text } : {}),
|
|
308
|
+
...(typeof body.blob === "string" ? { blob: body.blob } : {}),
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
return createMcpUiResourceReader({ readResource });
|
|
312
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/agent-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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,8 +28,8 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
31
|
+
"@silverprotocol/core": "0.4.1",
|
|
32
|
+
"@guuey/mcp-apps-host": "0.3.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": ">=18"
|
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,8 @@ export {
|
|
|
9
9
|
} from "./sse";
|
|
10
10
|
export { dismissLinkPrompt } from "./link-prompt";
|
|
11
11
|
export {
|
|
12
|
+
createUiResourceReader,
|
|
13
|
+
type CreateUiResourceReaderOptions,
|
|
12
14
|
createWebAdapters,
|
|
13
15
|
localStorageThreadStore,
|
|
14
16
|
webGenerateId,
|
|
@@ -27,40 +29,9 @@ export {
|
|
|
27
29
|
export { ingestMessageFrame } from "./blocks";
|
|
28
30
|
// Pure block-walk / resource-narrowing helpers for a block-preserving renderer
|
|
29
31
|
// (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";
|
|
32
|
+
// Transcript labeling/ordering helpers (mount narrowing itself moved to
|
|
33
|
+
// @guuey/mcp-apps-host — the SEP-1865 Host role package; import it directly).
|
|
34
|
+
export { sortHistoryCards, toolNameFor } from "./history";
|
|
64
35
|
// Re-export the AgJSON types the block-preserving transcript surfaces, so
|
|
65
36
|
// consumers can name `reduceResult` / block types without a direct
|
|
66
37
|
// `@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
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* functions — never at module load — so this file is import-safe under SSR
|
|
7
7
|
* (the functions guard on `typeof window`).
|
|
8
8
|
*/
|
|
9
|
+
import {
|
|
10
|
+
createMcpUiResourceReader,
|
|
11
|
+
type McpResourceReadResult,
|
|
12
|
+
type ResolvedViewMount,
|
|
13
|
+
} from "@guuey/mcp-apps-host";
|
|
9
14
|
import type {
|
|
10
15
|
AgentInvokeAdapters,
|
|
11
16
|
InvokeRequest,
|
|
@@ -317,3 +322,87 @@ export function createWebAdapters(
|
|
|
317
322
|
|
|
318
323
|
return adapters;
|
|
319
324
|
}
|
|
325
|
+
|
|
326
|
+
/** Options for {@link createUiResourceReader} — same credential surface as the history adapter. */
|
|
327
|
+
export interface CreateUiResourceReaderOptions {
|
|
328
|
+
/** The guuey public API base (`…/v1`). */
|
|
329
|
+
apiBaseUrl: string;
|
|
330
|
+
/** The thread whose persisted locators this reader may resolve. */
|
|
331
|
+
threadId: string;
|
|
332
|
+
/** Signed-in bearer — wins over the guest secret (same rule as the transport). */
|
|
333
|
+
getAccessToken?: (opts?: { forceRefresh?: boolean }) => Promise<string | null>;
|
|
334
|
+
/** Caller-owned anonymous guest secret (widget / guest chat). */
|
|
335
|
+
guestSecret?: string | null;
|
|
336
|
+
/** Injectable for tests. */
|
|
337
|
+
fetchImpl?: typeof fetch;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Build a `UiResourceReader` over guuey's authenticated resources/read proxy
|
|
342
|
+
* (guuey#122 Gap 1: `GET /v1/threads/:threadId/ui-resource?uri=…`).
|
|
343
|
+
*
|
|
344
|
+
* This is `@guuey/mcp-apps-host`'s `createMcpUiResourceReader` assembly over
|
|
345
|
+
* a guuey-platform transport (guuey#127) — channel resolution and payload
|
|
346
|
+
* narrowing live in the host package; only the transport is guuey-shaped.
|
|
347
|
+
* The proxy owns EVERYTHING trust-shaped: caller identity (the same three
|
|
348
|
+
* families as the history read), thread ownership, the locator-to-thread
|
|
349
|
+
* scope guard, and the per-user federation mint. This transport only carries
|
|
350
|
+
* the surface's existing credential and maps EVERY non-OK — 401/403/404/502
|
|
351
|
+
* alike — to `undefined`: deny is byte-identical to a miss, and a miss
|
|
352
|
+
* renders the host's placeholder, never an error surface.
|
|
353
|
+
*/
|
|
354
|
+
export function createUiResourceReader(
|
|
355
|
+
options: CreateUiResourceReaderOptions,
|
|
356
|
+
): (resourceUri: string) => Promise<ResolvedViewMount | undefined> {
|
|
357
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
358
|
+
const readResource = async (resourceUri: string): Promise<McpResourceReadResult | undefined> => {
|
|
359
|
+
const headers: Record<string, string> = {};
|
|
360
|
+
const token = options.getAccessToken ? await options.getAccessToken() : null;
|
|
361
|
+
const guest = sendableGuestSecret(options.guestSecret);
|
|
362
|
+
if (token) {
|
|
363
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
364
|
+
} else if (guest) {
|
|
365
|
+
headers[GUEST_HEADER] = guest;
|
|
366
|
+
}
|
|
367
|
+
const requestUrl = `${options.apiBaseUrl}/threads/${encodeURIComponent(options.threadId)}/ui-resource?uri=${encodeURIComponent(resourceUri)}`;
|
|
368
|
+
let res: Response;
|
|
369
|
+
try {
|
|
370
|
+
res = await fetchImpl(requestUrl, { headers });
|
|
371
|
+
} catch {
|
|
372
|
+
return undefined; // transport failure == miss == placeholder
|
|
373
|
+
}
|
|
374
|
+
// One forceRefresh retry on 401 with a bearer in play — the same
|
|
375
|
+
// expired-but-refreshable recovery the history adapter performs;
|
|
376
|
+
// without it a stale token degrades to a permanent placeholder.
|
|
377
|
+
if (res.status === 401 && options.getAccessToken) {
|
|
378
|
+
const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
|
|
379
|
+
if (fresh) {
|
|
380
|
+
try {
|
|
381
|
+
res = await fetchImpl(requestUrl, {
|
|
382
|
+
headers: { ...headers, authorization: `Bearer ${fresh}` },
|
|
383
|
+
});
|
|
384
|
+
} catch {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!res.ok) return undefined;
|
|
390
|
+
let body: { uri?: unknown; mimeType?: unknown; text?: unknown; blob?: unknown };
|
|
391
|
+
try {
|
|
392
|
+
body = (await res.json()) as typeof body;
|
|
393
|
+
} catch {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
// The proxy passes the blob arm through (a blob-only resource is not
|
|
397
|
+
// silently a miss — its route contract); mirror that here.
|
|
398
|
+
if (typeof body.uri !== "string") return undefined;
|
|
399
|
+
if (typeof body.text !== "string" && typeof body.blob !== "string") return undefined;
|
|
400
|
+
return {
|
|
401
|
+
uri: body.uri,
|
|
402
|
+
...(typeof body.mimeType === "string" ? { mimeType: body.mimeType } : {}),
|
|
403
|
+
...(typeof body.text === "string" ? { text: body.text } : {}),
|
|
404
|
+
...(typeof body.blob === "string" ? { blob: body.blob } : {}),
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
return createMcpUiResourceReader({ readResource });
|
|
408
|
+
}
|