@guuey/agent-client 0.7.2 → 0.8.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/error-codes.d.ts +6 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +6 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/invoke-turn.d.ts +1 -4
- package/dist/invoke-turn.d.ts.map +1 -1
- package/dist/invoke-turn.js +5 -10
- package/dist/react.d.ts +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/dist/sse.d.ts +6 -15
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +5 -24
- package/dist/types.d.ts +22 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/useAgentInvoke.d.ts +8 -0
- package/dist/useAgentInvoke.d.ts.map +1 -1
- package/dist/useAgentInvoke.js +20 -18
- package/dist/web-adapters.d.ts +53 -0
- package/dist/web-adapters.d.ts.map +1 -1
- package/dist/web-adapters.js +87 -0
- package/package.json +2 -2
- package/src/error-codes.ts +6 -0
- package/src/index.ts +14 -3
- package/src/invoke-turn.ts +6 -22
- package/src/react.ts +1 -0
- package/src/sse.ts +6 -23
- package/src/types.ts +22 -32
- package/src/useAgentInvoke.ts +23 -20
- package/src/web-adapters.ts +112 -0
package/dist/error-codes.d.ts
CHANGED
|
@@ -53,6 +53,12 @@ export declare const AGENT_ERROR_CODES: {
|
|
|
53
53
|
readonly DRAINING: "DRAINING";
|
|
54
54
|
/** Refused for this caller — e.g. the link-prompt dismiss route's byo-only rule. */
|
|
55
55
|
readonly FORBIDDEN: "FORBIDDEN";
|
|
56
|
+
/**
|
|
57
|
+
* The consent-answer door's deny==miss (guuey#207): the `AgHitlAnswer` names
|
|
58
|
+
* an ask this pod did not mint (wrong app, a thread the caller does not
|
|
59
|
+
* own, or a pod that takes no profile consent). Never an existence oracle.
|
|
60
|
+
*/
|
|
61
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
56
62
|
/** The turn ran past the pod's wall-clock budget. */
|
|
57
63
|
readonly TIMEOUT: "TIMEOUT";
|
|
58
64
|
/** A guuey-side dependency failed (not the agent's own code). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB;IAC5B,yDAAyD;;IAEzD,gDAAgD;;IAEhD,8DAA8D;;IAE9D;;;;;OAKG;;IAEH,6EAA6E;;IAE7E,qDAAqD;;IAErD;;;;OAIG;;IAEH;;;;;OAKG;;IAEH,oFAAoF;;IAEpF,qDAAqD;;IAErD,iEAAiE;;IAEjE,gCAAgC;;CAExB,CAAC;AAEX,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;;;;OAKG;;CAEK,CAAC;AAEX,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB;IAC5B,yDAAyD;;IAEzD,gDAAgD;;IAEhD,8DAA8D;;IAE9D;;;;;OAKG;;IAEH,6EAA6E;;IAE7E,qDAAqD;;IAErD;;;;OAIG;;IAEH;;;;;OAKG;;IAEH,oFAAoF;;IAEpF;;;;OAIG;;IAEH,qDAAqD;;IAErD,iEAAiE;;IAEjE,gCAAgC;;CAExB,CAAC;AAEX,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;;;;OAKG;;CAEK,CAAC;AAEX,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC"}
|
package/dist/error-codes.js
CHANGED
|
@@ -53,6 +53,12 @@ export const AGENT_ERROR_CODES = {
|
|
|
53
53
|
DRAINING: "DRAINING",
|
|
54
54
|
/** Refused for this caller — e.g. the link-prompt dismiss route's byo-only rule. */
|
|
55
55
|
FORBIDDEN: "FORBIDDEN",
|
|
56
|
+
/**
|
|
57
|
+
* The consent-answer door's deny==miss (guuey#207): the `AgHitlAnswer` names
|
|
58
|
+
* an ask this pod did not mint (wrong app, a thread the caller does not
|
|
59
|
+
* own, or a pod that takes no profile consent). Never an existence oracle.
|
|
60
|
+
*/
|
|
61
|
+
NOT_FOUND: "NOT_FOUND",
|
|
56
62
|
/** The turn ran past the pod's wall-clock budget. */
|
|
57
63
|
TIMEOUT: "TIMEOUT",
|
|
58
64
|
/** A guuey-side dependency failed (not the agent's own code). */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField,
|
|
1
|
+
export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField, parseLinkRequest, type ParsedSseEvent, } from "./sse.js";
|
|
2
2
|
export { dismissLinkPrompt } from "./link-prompt.js";
|
|
3
3
|
export { invokeTurn, toInvokeUrl, type InvokeTurnEvent } from "./invoke-turn.js";
|
|
4
|
-
export { createUiActionRelay, type CreateUiActionRelayOptions, createUiResourceReader, type CreateUiResourceReaderOptions, createWebAdapters, localStorageThreadStore, webGenerateId, type CreateWebAdaptersOptions, } from "./web-adapters.js";
|
|
4
|
+
export { createHitlAnswerRelay, type CreateHitlAnswerRelayOptions, type HitlAnswerRelayResult, createUiActionRelay, type CreateUiActionRelayOptions, createUiResourceReader, type CreateUiResourceReaderOptions, createWebAdapters, localStorageThreadStore, webGenerateId, type CreateWebAdaptersOptions, } from "./web-adapters.js";
|
|
5
5
|
export { fetchStreamTransport, sendableGuestSecret, GUEST_HEADER, withActivityObserver, type FetchStreamTransportOptions, } from "./transport.js";
|
|
6
6
|
export { withSaturationRetry, withColdStartRetry, parseRetryAfterSeconds, type SaturationRetryOptions, type ColdStartRetryOptions, } from "./saturation-retry.js";
|
|
7
7
|
export { AgentResponseError } from "./errors.js";
|
|
@@ -10,6 +10,6 @@ export { fetchThreadHistory, threadHistoryRowsToMessages, threadHistoryRowsToCar
|
|
|
10
10
|
export { ingestMessageFrame } from "./blocks.js";
|
|
11
11
|
export { sortHistoryCards, toolNameFor } from "./history.js";
|
|
12
12
|
export { Reducer } from "@silverprotocol/core";
|
|
13
|
-
export type { AgEvent, AgReduceResult, AgMessage, AgBlock } from "@silverprotocol/core";
|
|
14
|
-
export type { AgentMessage, HistoryCard,
|
|
13
|
+
export type { AgEvent, AgReduceResult, AgMessage, AgBlock, AgClientCapabilities, AgHitlAnswer, } from "@silverprotocol/core";
|
|
14
|
+
export type { AgentMessage, HistoryCard, ProfileLinkRequest, ThreadIdStore, GenerateId, InvokeRequest, InvokeTransport, AgentInvokeAdapters, AgentInvokeHistoryAdapter, AgentInvokeStatus, HistoryLoadResult, StallRecoveryOptions, UseAgentInvokeOptions, UseAgentInvokeReturn, } from "./types.js";
|
|
15
15
|
//# 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,
|
|
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,gBAAgB,EAChB,KAAK,cAAc,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EACL,iBAAiB,EACjB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,eAAe,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKjD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAM7D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EACV,OAAO,EACP,cAAc,EACd,SAAS,EACT,OAAO,EAIP,oBAAoB,EACpB,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField,
|
|
1
|
+
export { parseSseEvents, extractAssistantText, reduceAssistantText, stringField, parseLinkRequest, } from "./sse.js";
|
|
2
2
|
export { dismissLinkPrompt } from "./link-prompt.js";
|
|
3
3
|
// One agent turn as a pure async generator — the wire walk `useAgentInvoke`
|
|
4
4
|
// wraps, for hosts that drive their own turn state machine (guuey#186 G5).
|
|
5
5
|
export { invokeTurn, toInvokeUrl } from "./invoke-turn.js";
|
|
6
|
-
export { createUiActionRelay, createUiResourceReader, createWebAdapters, localStorageThreadStore, webGenerateId, } from "./web-adapters.js";
|
|
6
|
+
export { createHitlAnswerRelay, createUiActionRelay, createUiResourceReader, createWebAdapters, localStorageThreadStore, webGenerateId, } from "./web-adapters.js";
|
|
7
7
|
// The invoke transport + guest-identity wire pieces, in their own
|
|
8
8
|
// mcp-apps-host-free module. Consumers that want ONLY this graph (no
|
|
9
9
|
// host-role card layer riding along) import `@guuey/agent-client/transport`
|
package/dist/invoke-turn.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* fall-through, so new wire events are additive for every consumer.
|
|
24
24
|
*/
|
|
25
25
|
import type { AgEvent } from "@silverprotocol/core";
|
|
26
|
-
import type { AgentInvokeStatus, InvokeRequest, InvokeTransport,
|
|
26
|
+
import type { AgentInvokeStatus, InvokeRequest, InvokeTransport, ProfileLinkRequest } from "./types.js";
|
|
27
27
|
/**
|
|
28
28
|
* One semantic step of a turn. Field conventions:
|
|
29
29
|
*
|
|
@@ -51,9 +51,6 @@ export type InvokeTurnEvent = {
|
|
|
51
51
|
kind: "error";
|
|
52
52
|
message: string;
|
|
53
53
|
code: string | null;
|
|
54
|
-
} | {
|
|
55
|
-
kind: "profile-consent";
|
|
56
|
-
request: ProfileConsentRequest;
|
|
57
54
|
} | {
|
|
58
55
|
kind: "profile-link";
|
|
59
56
|
request: ProfileLinkRequest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke-turn.d.ts","sourceRoot":"","sources":["../src/invoke-turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"invoke-turn.d.ts","sourceRoot":"","sources":["../src/invoke-turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAExG;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC5C;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,EAAE,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC,CAAC;IAC9E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAuB,UAAU,CAC/B,GAAG,EAAE,aAAa,EAClB,SAAS,EAAE,eAAe,GACzB,cAAc,CAAC,eAAe,CAAC,CAuEjC"}
|
package/dist/invoke-turn.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseLinkRequest, parseSseEvents, reduceAssistantText, stringField } from "./sse.js";
|
|
2
2
|
import { ingestMessageFrame } from "./blocks.js";
|
|
3
3
|
/**
|
|
4
4
|
* Normalize an agent endpoint to its invoke URL (guuey#186 G3). Accepts BOTH
|
|
@@ -97,17 +97,12 @@ export async function* invokeTurn(req, transport) {
|
|
|
97
97
|
code: stringField(ev.data, "code") ?? null,
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
else if (ev.event === "profile-consent-needed") {
|
|
101
|
-
// Cross-app profile consent ask (T6). Only a well-formed payload
|
|
102
|
-
// yields; a malformed one is dropped, leaving any prior valid
|
|
103
|
-
// request untouched (never clobbered to null).
|
|
104
|
-
const parsed = parseConsentRequest(ev.data);
|
|
105
|
-
if (parsed)
|
|
106
|
-
yield { kind: "profile-consent", request: parsed };
|
|
107
|
-
}
|
|
108
100
|
else if (ev.event === "profile-link-needed") {
|
|
109
101
|
// Cross-app profile LINK invite (linkcoh T3) for an unlinked byo
|
|
110
|
-
// caller.
|
|
102
|
+
// caller. Only a well-formed payload yields; a malformed one is
|
|
103
|
+
// dropped, leaving any prior valid request untouched (never clobbered
|
|
104
|
+
// to null). (Consent is NOT a bespoke event: it rides the AgJSON fold
|
|
105
|
+
// as `hitl.ask` + `turn.done outcome:"paused"` — guuey#207.)
|
|
111
106
|
const parsed = parseLinkRequest(ev.data);
|
|
112
107
|
if (parsed)
|
|
113
108
|
yield { kind: "profile-link", request: parsed };
|
package/dist/react.d.ts
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
* history reader, and the web adapters). Consumers that only need those never
|
|
7
7
|
* import React at all; consumers that render chat import the hook from here.
|
|
8
8
|
*/
|
|
9
|
-
export { useAgentInvoke, applyHistoryResult, type HistoryApplication, stallProbeDecision, STALL_RECOVERY_DEFAULTS, } from "./useAgentInvoke.js";
|
|
9
|
+
export { useAgentInvoke, applyHistoryResult, type HistoryApplication, stallProbeDecision, STALL_RECOVERY_DEFAULTS, DEFAULT_BLOCK_PRESERVING_CAPABILITIES, } from "./useAgentInvoke.js";
|
|
10
10
|
export type { AgEvent, AgReduceResult } from "@silverprotocol/core";
|
|
11
11
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,uBAAuB,EACvB,qCAAqC,GACtC,MAAM,qBAAqB,CAAC;AAI7B,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/react.js
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* history reader, and the web adapters). Consumers that only need those never
|
|
7
7
|
* import React at all; consumers that render chat import the hook from here.
|
|
8
8
|
*/
|
|
9
|
-
export { useAgentInvoke, applyHistoryResult, stallProbeDecision, STALL_RECOVERY_DEFAULTS, } from "./useAgentInvoke.js";
|
|
9
|
+
export { useAgentInvoke, applyHistoryResult, stallProbeDecision, STALL_RECOVERY_DEFAULTS, DEFAULT_BLOCK_PRESERVING_CAPABILITIES, } from "./useAgentInvoke.js";
|
package/dist/sse.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* platform dependencies — unit-tested in isolation (`sse.test.ts`) and shared
|
|
4
4
|
* verbatim across web (Studio) and React-Native (Portal).
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { ProfileLinkRequest } from "./types.js";
|
|
7
7
|
export interface ParsedSseEvent {
|
|
8
8
|
event: string;
|
|
9
9
|
data: unknown;
|
|
@@ -46,22 +46,13 @@ export declare function reduceAssistantText(current: string, data: unknown): str
|
|
|
46
46
|
export declare function extractAssistantText(data: unknown): string;
|
|
47
47
|
/** Read a string field off an SSE `data` object, or undefined. */
|
|
48
48
|
export declare function stringField(data: unknown, key: string): string | undefined;
|
|
49
|
-
/**
|
|
50
|
-
* Parse a `profile-consent-needed` SSE payload into a typed
|
|
51
|
-
* {@link ProfileConsentRequest}, or `null` if it does not conform. `appId`
|
|
52
|
-
* must be a non-empty string and `requested` exactly `"read"` or
|
|
53
|
-
* `"read-write"`; extra keys are tolerated (ignored). Returns a fresh
|
|
54
|
-
* normalized object so callers get exactly the typed shape, never the raw
|
|
55
|
-
* wire payload with unknown extras.
|
|
56
|
-
*/
|
|
57
|
-
export declare function parseConsentRequest(data: unknown): ProfileConsentRequest | null;
|
|
58
49
|
/**
|
|
59
50
|
* Parse a `profile-link-needed` SSE payload into a typed
|
|
60
|
-
* {@link ProfileLinkRequest}, or `null` if it does not conform.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
51
|
+
* {@link ProfileLinkRequest}, or `null` if it does not conform. `appId` must
|
|
52
|
+
* be a non-empty string and `requested` exactly `"read"` or `"read-write"`;
|
|
53
|
+
* extra keys are tolerated (ignored). Returns a fresh normalized object so
|
|
54
|
+
* callers get exactly the typed shape, never the raw wire payload with
|
|
55
|
+
* unknown extras.
|
|
65
56
|
*/
|
|
66
57
|
export declare function parseLinkRequest(data: unknown): ProfileLinkRequest | null;
|
|
67
58
|
//# sourceMappingURL=sse.d.ts.map
|
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,
|
|
1
|
+
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,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,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAOzE"}
|
package/dist/sse.js
CHANGED
|
@@ -134,32 +134,13 @@ export function stringField(data, key) {
|
|
|
134
134
|
const v = data[key];
|
|
135
135
|
return typeof v === "string" ? v : undefined;
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Parse a `profile-consent-needed` SSE payload into a typed
|
|
139
|
-
* {@link ProfileConsentRequest}, or `null` if it does not conform. `appId`
|
|
140
|
-
* must be a non-empty string and `requested` exactly `"read"` or
|
|
141
|
-
* `"read-write"`; extra keys are tolerated (ignored). Returns a fresh
|
|
142
|
-
* normalized object so callers get exactly the typed shape, never the raw
|
|
143
|
-
* wire payload with unknown extras.
|
|
144
|
-
*/
|
|
145
|
-
export function parseConsentRequest(data) {
|
|
146
|
-
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
147
|
-
return null;
|
|
148
|
-
const appId = data.appId;
|
|
149
|
-
const requested = data.requested;
|
|
150
|
-
if (typeof appId !== "string" || appId.length === 0)
|
|
151
|
-
return null;
|
|
152
|
-
if (requested !== "read" && requested !== "read-write")
|
|
153
|
-
return null;
|
|
154
|
-
return { appId, requested };
|
|
155
|
-
}
|
|
156
137
|
/**
|
|
157
138
|
* Parse a `profile-link-needed` SSE payload into a typed
|
|
158
|
-
* {@link ProfileLinkRequest}, or `null` if it does not conform.
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
139
|
+
* {@link ProfileLinkRequest}, or `null` if it does not conform. `appId` must
|
|
140
|
+
* be a non-empty string and `requested` exactly `"read"` or `"read-write"`;
|
|
141
|
+
* extra keys are tolerated (ignored). Returns a fresh normalized object so
|
|
142
|
+
* callers get exactly the typed shape, never the raw wire payload with
|
|
143
|
+
* unknown extras.
|
|
163
144
|
*/
|
|
164
145
|
export function parseLinkRequest(data) {
|
|
165
146
|
if (typeof data !== "object" || data === null || Array.isArray(data))
|
package/dist/types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `createWebAdapters`'s `getGuestSecret`. This mirrors the ggui
|
|
13
13
|
* `MessageStorageAdapter` injection pattern.
|
|
14
14
|
*/
|
|
15
|
-
import type { AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
15
|
+
import type { AgClientCapabilities, AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
16
16
|
/** A flat chat turn as rendered by the consumer UI. */
|
|
17
17
|
export interface AgentMessage {
|
|
18
18
|
role: "user" | "assistant";
|
|
@@ -25,19 +25,6 @@ export interface AgentMessage {
|
|
|
25
25
|
*/
|
|
26
26
|
clientMessageId?: string;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* A cross-app profile consent request surfaced mid-stream by the pod's
|
|
30
|
-
* `profile-consent-needed` SSE event (nocode-runtime T6). Emitted when the
|
|
31
|
-
* agent declares a profile intent the caller has NOT yet granted for this app,
|
|
32
|
-
* so the consumer UI can prompt the user to authorize `read` or `read-write`
|
|
33
|
-
* access. `requested` mirrors the pod's `ProfileAccess` posture verbatim; the
|
|
34
|
-
* literal union is inlined rather than imported to keep this client SDK free of
|
|
35
|
-
* any backend-package dependency.
|
|
36
|
-
*/
|
|
37
|
-
export interface ProfileConsentRequest {
|
|
38
|
-
appId: string;
|
|
39
|
-
requested: "read" | "read-write";
|
|
40
|
-
}
|
|
41
28
|
/**
|
|
42
29
|
* A cross-app profile LINK invite surfaced mid-stream by the pod's
|
|
43
30
|
* `profile-link-needed` SSE event (nocode-runtime linkcoh T3). Emitted when an
|
|
@@ -45,8 +32,10 @@ export interface ProfileConsentRequest {
|
|
|
45
32
|
* link their guuey account (via the named `/link` ceremony) so they earn the
|
|
46
33
|
* guuey-wide cross-app profile. `requested` mirrors the pod's `ProfileAccess`
|
|
47
34
|
* posture verbatim (the builder's declared access, not a live ask) — the
|
|
48
|
-
* literal union is inlined rather than imported
|
|
49
|
-
*
|
|
35
|
+
* literal union is inlined rather than imported to keep this client SDK free
|
|
36
|
+
* of any backend-package dependency. (Consent itself is no longer a bespoke
|
|
37
|
+
* event: it rides AgJSON `hitl.ask` + `turn.done outcome:"paused"` in the
|
|
38
|
+
* fold and is answered through `createHitlAnswerRelay` — guuey#207.)
|
|
50
39
|
*/
|
|
51
40
|
export interface ProfileLinkRequest {
|
|
52
41
|
appId: string;
|
|
@@ -79,7 +68,7 @@ export type GenerateId = () => string;
|
|
|
79
68
|
export interface InvokeRequest {
|
|
80
69
|
/** Fully-resolved POST target (already normalised to end in `/agent/invoke`). */
|
|
81
70
|
url: string;
|
|
82
|
-
/** JSON request body: `{ input, threadId?, clientMessageId }`. */
|
|
71
|
+
/** JSON request body: `{ input, threadId?, clientMessageId, capabilities? }`. */
|
|
83
72
|
body: unknown;
|
|
84
73
|
/** Aborts the in-flight stream. */
|
|
85
74
|
signal: AbortSignal;
|
|
@@ -154,6 +143,18 @@ export interface UseAgentInvokeOptions {
|
|
|
154
143
|
* reducer is never constructed and the text behaviour is byte-identical.
|
|
155
144
|
*/
|
|
156
145
|
preserveBlocks?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* The AgJSON client capabilities advertised on every invoke body (spec §3
|
|
148
|
+
* `AgClientCapabilities`, guuey#207). The pod reads them to decide what it
|
|
149
|
+
* may ask: today it declares consent `grantModes` (the three-mode profile
|
|
150
|
+
* grant the `@guuey/chat` card renders) ONLY to a client that advertised
|
|
151
|
+
* `hitl.grantModes` — a client that cannot render the decision surface is
|
|
152
|
+
* simply not asked. Default: when {@link preserveBlocks} is on,
|
|
153
|
+
* `{ hitl: { ask: true, grantModes: true } }` (a block-preserving consumer
|
|
154
|
+
* folds the paused turn the ask rides on — the kit's exact path); when off,
|
|
155
|
+
* nothing is advertised. Pass an explicit object to override either way.
|
|
156
|
+
*/
|
|
157
|
+
capabilities?: AgClientCapabilities;
|
|
157
158
|
/**
|
|
158
159
|
* Stall recovery for a half-dead stream (guuey#192). A connection that dies
|
|
159
160
|
* WITHOUT erroring (TCP alive, zero bytes, no `done`) would otherwise leave
|
|
@@ -270,26 +271,16 @@ export interface UseAgentInvokeReturn {
|
|
|
270
271
|
* `reset()` clears it back to `[]`.
|
|
271
272
|
*/
|
|
272
273
|
historyCards: HistoryCard[];
|
|
273
|
-
/**
|
|
274
|
-
* The latest cross-app profile consent request the pod asked for on THIS
|
|
275
|
-
* conversation, or `null`. Set from a well-formed `profile-consent-needed`
|
|
276
|
-
* SSE event (see {@link ProfileConsentRequest}); malformed payloads are
|
|
277
|
-
* dropped and leave the field untouched. `reset()` and an app switch clear
|
|
278
|
-
* it back to `null`. Consumers that never render a consent prompt (e.g.
|
|
279
|
-
* Studio) simply ignore this field.
|
|
280
|
-
*/
|
|
281
|
-
profileConsentRequest: ProfileConsentRequest | null;
|
|
282
|
-
/** Dismiss the pending {@link profileConsentRequest} (back to `null`). */
|
|
283
|
-
clearProfileConsentRequest: () => void;
|
|
284
274
|
/**
|
|
285
275
|
* The latest cross-app profile LINK invite the pod asked for on THIS
|
|
286
276
|
* conversation, or `null`. Set from a well-formed `profile-link-needed`
|
|
287
277
|
* SSE event (see {@link ProfileLinkRequest}); malformed payloads are
|
|
288
278
|
* dropped and leave the field untouched. `reset()` and an app switch clear
|
|
289
279
|
* it back to `null`. Consumers that never render a link prompt simply
|
|
290
|
-
* ignore this field. Distinct from
|
|
291
|
-
* invites an UNLINKED byo user to link their
|
|
292
|
-
* already-linked user to grant an app
|
|
280
|
+
* ignore this field. Distinct from consent (an AgJSON `hitl.ask` in the
|
|
281
|
+
* fold, guuey#207): this one invites an UNLINKED byo user to link their
|
|
282
|
+
* account; consent asks an already-linked user to grant an app
|
|
283
|
+
* read/read-write access.
|
|
293
284
|
*/
|
|
294
285
|
profileLinkRequest: ProfileLinkRequest | null;
|
|
295
286
|
/** Dismiss the pending {@link profileLinkRequest} (back to `null`). */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/useAgentInvoke.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { type AgClientCapabilities } from "@silverprotocol/core";
|
|
1
2
|
import type { AgentMessage, HistoryLoadResult, UseAgentInvokeOptions, UseAgentInvokeReturn } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a block-preserving consumer advertises by default (guuey#207): it
|
|
5
|
+
* folds `turn.done outcome:"paused"` records, so it can render the AgJSON
|
|
6
|
+
* hitl card with declared grant modes — the `@guuey/chat` path. See
|
|
7
|
+
* `UseAgentInvokeOptions.capabilities`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_BLOCK_PRESERVING_CAPABILITIES: AgClientCapabilities;
|
|
2
10
|
/** The decision `applyHistoryResult` reaches for a loaded transcript. */
|
|
3
11
|
export type HistoryApplication = {
|
|
4
12
|
kind: "seed";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAW,KAAK,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAK/F,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,EAAE,oBAEnD,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,eAAe,EAAE,YAAY,EAAE,GAC9B,kBAAkB,CAIpB;AAED,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;CAAkD,CAAC;AAYvF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,WAAW,CAOvB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CA6dhF"}
|
package/dist/useAgentInvoke.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ggui generative-UI protocol that `@ggui-ai/mcp-apps-react`'s useInvoke targets):
|
|
6
6
|
*
|
|
7
7
|
* POST {endpointUrl}/agent/invoke
|
|
8
|
-
* body: { input, threadId?, clientMessageId }
|
|
8
|
+
* body: { input, threadId?, clientMessageId, capabilities? }
|
|
9
9
|
* ← SSE:
|
|
10
10
|
* event: session { sessionId, userId, threadId? }
|
|
11
11
|
* event: message <SDKMessage JSON> (assistant turns + result)
|
|
@@ -30,6 +30,15 @@ import { CLIENT_ERROR_CODES } from "./error-codes.js";
|
|
|
30
30
|
function threadStorageKey(appId) {
|
|
31
31
|
return `guuey:thread:${appId ?? "default"}`;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* What a block-preserving consumer advertises by default (guuey#207): it
|
|
35
|
+
* folds `turn.done outcome:"paused"` records, so it can render the AgJSON
|
|
36
|
+
* hitl card with declared grant modes — the `@guuey/chat` path. See
|
|
37
|
+
* `UseAgentInvokeOptions.capabilities`.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_BLOCK_PRESERVING_CAPABILITIES = {
|
|
40
|
+
hitl: { ask: true, grantModes: true },
|
|
41
|
+
};
|
|
33
42
|
/**
|
|
34
43
|
* Pure decision seam for post-hydration history application (see
|
|
35
44
|
* `AgentInvokeHistoryAdapter` in `./types`). `gone` always clears the
|
|
@@ -111,14 +120,11 @@ export function useAgentInvoke(opts) {
|
|
|
111
120
|
// contract). Independent of the live `reduceResult` fold — populated only
|
|
112
121
|
// when a card-carrying history load seeds the transcript.
|
|
113
122
|
const [historyCards, setHistoryCards] = useState([]);
|
|
114
|
-
// The pod's latest cross-app profile consent ask on this conversation (T6's
|
|
115
|
-
// `profile-consent-needed` SSE event), or null. Cleared on app switch /
|
|
116
|
-
// reset / explicit dismiss. Consumers with no consent UI just ignore it.
|
|
117
|
-
const [profileConsentRequest, setProfileConsentRequest] = useState(null);
|
|
118
123
|
// The pod's latest cross-app profile LINK invite on this conversation (T3's
|
|
119
124
|
// `profile-link-needed` SSE event), or null. Cleared on app switch / reset /
|
|
120
|
-
// explicit dismiss
|
|
121
|
-
//
|
|
125
|
+
// explicit dismiss. (Consent is NOT hook state: it rides the AgJSON fold as
|
|
126
|
+
// a paused turn — `reduceResult` — and is answered through
|
|
127
|
+
// `createHitlAnswerRelay`, guuey#207.)
|
|
122
128
|
const [profileLinkRequest, setProfileLinkRequest] = useState(null);
|
|
123
129
|
// The last turn's ending posture + the optimistic-send ledger — the
|
|
124
130
|
// transcript renderer's inputs (guuey#135 wave 3b; see the return-type
|
|
@@ -174,8 +180,7 @@ export function useAgentInvoke(opts) {
|
|
|
174
180
|
reducerRef.current = null;
|
|
175
181
|
setReduceResult(null);
|
|
176
182
|
setHistoryCards([]);
|
|
177
|
-
// A prior app's
|
|
178
|
-
setProfileConsentRequest(null);
|
|
183
|
+
// A prior app's link invite must never leak into the new conversation.
|
|
179
184
|
setProfileLinkRequest(null);
|
|
180
185
|
setAborted(false);
|
|
181
186
|
setAdopted(false);
|
|
@@ -268,15 +273,11 @@ export function useAgentInvoke(opts) {
|
|
|
268
273
|
reducerRef.current = null;
|
|
269
274
|
setReduceResult(null);
|
|
270
275
|
setHistoryCards([]);
|
|
271
|
-
setProfileConsentRequest(null);
|
|
272
276
|
setProfileLinkRequest(null);
|
|
273
277
|
setAborted(false);
|
|
274
278
|
setAdopted(false);
|
|
275
279
|
setSendStates({});
|
|
276
280
|
}, [appId]);
|
|
277
|
-
const clearProfileConsentRequest = useCallback(() => {
|
|
278
|
-
setProfileConsentRequest(null);
|
|
279
|
-
}, []);
|
|
280
281
|
const clearProfileLinkRequest = useCallback(() => {
|
|
281
282
|
setProfileLinkRequest(null);
|
|
282
283
|
}, []);
|
|
@@ -450,10 +451,16 @@ export function useAgentInvoke(opts) {
|
|
|
450
451
|
: adapters.transport;
|
|
451
452
|
try {
|
|
452
453
|
const invokeUrl = toInvokeUrl(endpointUrl);
|
|
454
|
+
// The advertised AgJSON client capabilities (spec §3, guuey#207): an
|
|
455
|
+
// explicit option wins; else a block-preserving consumer advertises
|
|
456
|
+
// the hitl grant-mode card it can render, and a text-only one nothing.
|
|
457
|
+
const capabilities = opts.capabilities ??
|
|
458
|
+
(preserveBlocksRef.current ? DEFAULT_BLOCK_PRESERVING_CAPABILITIES : undefined);
|
|
453
459
|
const body = {
|
|
454
460
|
input,
|
|
455
461
|
...(threadIdRef.current ? { threadId: threadIdRef.current } : {}),
|
|
456
462
|
clientMessageId,
|
|
463
|
+
...(capabilities !== undefined ? { capabilities } : {}),
|
|
457
464
|
};
|
|
458
465
|
// The wire walk lives in `invokeTurn` (the pure per-turn generator —
|
|
459
466
|
// its docblock owns the switch semantics); this hook only maps each
|
|
@@ -498,9 +505,6 @@ export function useAgentInvoke(opts) {
|
|
|
498
505
|
setError(ev.message);
|
|
499
506
|
setErrorCode(ev.code);
|
|
500
507
|
}
|
|
501
|
-
else if (ev.kind === "profile-consent") {
|
|
502
|
-
setProfileConsentRequest(ev.request);
|
|
503
|
-
}
|
|
504
508
|
else if (ev.kind === "profile-link") {
|
|
505
509
|
setProfileLinkRequest(ev.request);
|
|
506
510
|
}
|
|
@@ -569,8 +573,6 @@ export function useAgentInvoke(opts) {
|
|
|
569
573
|
reset,
|
|
570
574
|
reduceResult,
|
|
571
575
|
historyCards,
|
|
572
|
-
profileConsentRequest,
|
|
573
|
-
clearProfileConsentRequest,
|
|
574
576
|
profileLinkRequest,
|
|
575
577
|
clearProfileLinkRequest,
|
|
576
578
|
aborted,
|
package/dist/web-adapters.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* (the functions guard on `typeof window`).
|
|
8
8
|
*/
|
|
9
9
|
import { type McpToolCallResult, type ResolvedViewMount, type UiActionRequest } from "@guuey/mcp-apps-host";
|
|
10
|
+
import type { AgHitlAnswer } from "@silverprotocol/core";
|
|
10
11
|
import type { AgentInvokeAdapters, ThreadIdStore } from "./types.js";
|
|
11
12
|
/** Persists the threadId in `window.localStorage` (synchronously). */
|
|
12
13
|
export declare const localStorageThreadStore: ThreadIdStore;
|
|
@@ -198,4 +199,56 @@ export interface CreateUiActionRelayOptions {
|
|
|
198
199
|
* error into the sandbox bridge.
|
|
199
200
|
*/
|
|
200
201
|
export declare function createUiActionRelay(options: CreateUiActionRelayOptions): (request: UiActionRequest) => Promise<McpToolCallResult>;
|
|
202
|
+
/** Options for {@link createHitlAnswerRelay} — the same credential surface as the card relays. */
|
|
203
|
+
export interface CreateHitlAnswerRelayOptions {
|
|
204
|
+
/** The surface's invoke endpoint (pod base URL or full `/agent/invoke` URL) — the answer door lives on the pod. */
|
|
205
|
+
endpointUrl: string;
|
|
206
|
+
/** Signed-in bearer — wins over the guest secret (same rule as the transport). */
|
|
207
|
+
getAccessToken?: (opts?: {
|
|
208
|
+
forceRefresh?: boolean;
|
|
209
|
+
}) => Promise<string | null>;
|
|
210
|
+
/** Caller-owned anonymous guest secret (widget / guest chat). */
|
|
211
|
+
guestSecret?: string | null;
|
|
212
|
+
/** Injectable for tests. */
|
|
213
|
+
fetchImpl?: typeof fetch;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The pod's answer to a delivered {@link AgHitlAnswer}. `ok` carries the
|
|
217
|
+
* body the door returns (`askId`, echoed `status`, and — for a recorded
|
|
218
|
+
* consent — the grant `mode` written); every non-2xx collapses to the pod's
|
|
219
|
+
* `{ code, message }` envelope (the same vocabulary as `AGENT_ERROR_CODES`,
|
|
220
|
+
* e.g. `NOT_FOUND` for an ask this pod did not mint, `INVALID_REQUEST` for a
|
|
221
|
+
* spec-invalid answer) with the HTTP status; a transport failure is
|
|
222
|
+
* `status: 0` with a null code.
|
|
223
|
+
*/
|
|
224
|
+
export type HitlAnswerRelayResult = {
|
|
225
|
+
ok: true;
|
|
226
|
+
body: {
|
|
227
|
+
askId: string;
|
|
228
|
+
status: AgHitlAnswer["status"];
|
|
229
|
+
mode?: string;
|
|
230
|
+
};
|
|
231
|
+
} | {
|
|
232
|
+
ok: false;
|
|
233
|
+
status: number;
|
|
234
|
+
code: string | null;
|
|
235
|
+
message: string;
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Build the client→pod channel for AgJSON HITL answers (guuey#207): `POST
|
|
239
|
+
* <pod>/agent/hitl-answer` with the spec {@link AgHitlAnswer} the kit's
|
|
240
|
+
* `answerHitlPrompt` constructed (already validated against the ask's
|
|
241
|
+
* persisted declaration). The pod owns EVERYTHING trust-shaped — caller
|
|
242
|
+
* identity (the same three families as the invoke), which ask it minted,
|
|
243
|
+
* the thread a `once` grant binds to, the access level written — this
|
|
244
|
+
* transport only carries the surface's existing credential under the
|
|
245
|
+
* one-carrier rule (bearer → guest header → cookie), with the card relays'
|
|
246
|
+
* single 401 forceRefresh retry.
|
|
247
|
+
*
|
|
248
|
+
* Today the only producer is the pod's cross-app profile consent ask (the
|
|
249
|
+
* three-mode grant), whose answer resolves into the caller's own
|
|
250
|
+
* `ProfileGrant` row; the channel is generic by construction — any future
|
|
251
|
+
* `hitl.ask` the runtime emits is answered through this same door.
|
|
252
|
+
*/
|
|
253
|
+
export declare function createHitlAnswerRelay(options: CreateHitlAnswerRelayOptions): (answer: AgHitlAnswer) => Promise<HitlAnswerRelayResult>;
|
|
201
254
|
//# 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,EAIL,KAAK,iBAAiB,EAEtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,mBAAmB,EAAmB,aAAa,EAAE,MAAM,YAAY,CAAC;AAKtF,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,EAAE,aAiBrC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,MAAM,CAKtC;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,CA8ErB;AAED,mGAAmG;AACnG,MAAM,WAAW,6BAA6B;IAC5C,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,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;AAID,0FAA0F;AAC1F,wBAAgB,4BAA4B,IAAI,IAAI,CAEnD;AAcD,0FAA0F;AAC1F,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,6BAA6B,GACrC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAsFjE;AAED,uFAAuF;AACvF,MAAM,WAAW,0BAA0B;IACzC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,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;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAuG1D"}
|
|
1
|
+
{"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/web-adapters.js
CHANGED
|
@@ -368,3 +368,90 @@ export function createUiActionRelay(options) {
|
|
|
368
368
|
};
|
|
369
369
|
return createMcpUiActionRelay({ callTool });
|
|
370
370
|
}
|
|
371
|
+
/** `<pod base>/agent/hitl-answer` — the AgJSON HITL answer door (guuey#207). */
|
|
372
|
+
function toHitlAnswerUrl(endpointUrl) {
|
|
373
|
+
return toInvokeUrl(endpointUrl).replace(/\/agent\/invoke$/, "/agent/hitl-answer");
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Build the client→pod channel for AgJSON HITL answers (guuey#207): `POST
|
|
377
|
+
* <pod>/agent/hitl-answer` with the spec {@link AgHitlAnswer} the kit's
|
|
378
|
+
* `answerHitlPrompt` constructed (already validated against the ask's
|
|
379
|
+
* persisted declaration). The pod owns EVERYTHING trust-shaped — caller
|
|
380
|
+
* identity (the same three families as the invoke), which ask it minted,
|
|
381
|
+
* the thread a `once` grant binds to, the access level written — this
|
|
382
|
+
* transport only carries the surface's existing credential under the
|
|
383
|
+
* one-carrier rule (bearer → guest header → cookie), with the card relays'
|
|
384
|
+
* single 401 forceRefresh retry.
|
|
385
|
+
*
|
|
386
|
+
* Today the only producer is the pod's cross-app profile consent ask (the
|
|
387
|
+
* three-mode grant), whose answer resolves into the caller's own
|
|
388
|
+
* `ProfileGrant` row; the channel is generic by construction — any future
|
|
389
|
+
* `hitl.ask` the runtime emits is answered through this same door.
|
|
390
|
+
*/
|
|
391
|
+
export function createHitlAnswerRelay(options) {
|
|
392
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
393
|
+
const url = toHitlAnswerUrl(options.endpointUrl);
|
|
394
|
+
return async (answer) => {
|
|
395
|
+
const headers = { "content-type": "application/json" };
|
|
396
|
+
const body = JSON.stringify(answer);
|
|
397
|
+
const init = { method: "POST", headers, body };
|
|
398
|
+
const token = options.getAccessToken ? await options.getAccessToken() : null;
|
|
399
|
+
const guest = sendableGuestSecret(options.guestSecret);
|
|
400
|
+
if (token) {
|
|
401
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
402
|
+
}
|
|
403
|
+
else if (guest) {
|
|
404
|
+
headers[GUEST_HEADER] = guest;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
init.credentials = "include";
|
|
408
|
+
}
|
|
409
|
+
let res;
|
|
410
|
+
try {
|
|
411
|
+
res = await fetchImpl(url, init);
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
return { ok: false, status: 0, code: null, message: err instanceof Error ? err.message : String(err) };
|
|
415
|
+
}
|
|
416
|
+
if (res.status === 401 && options.getAccessToken) {
|
|
417
|
+
const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
|
|
418
|
+
if (fresh) {
|
|
419
|
+
try {
|
|
420
|
+
res = await fetchImpl(url, {
|
|
421
|
+
method: "POST",
|
|
422
|
+
headers: { ...headers, authorization: `Bearer ${fresh}` },
|
|
423
|
+
body,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
return { ok: false, status: 0, code: null, message: err instanceof Error ? err.message : String(err) };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
let parsed = undefined;
|
|
432
|
+
try {
|
|
433
|
+
parsed = await res.json();
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
parsed = undefined;
|
|
437
|
+
}
|
|
438
|
+
if (res.ok) {
|
|
439
|
+
const b = (parsed ?? {});
|
|
440
|
+
return {
|
|
441
|
+
ok: true,
|
|
442
|
+
body: {
|
|
443
|
+
askId: typeof b.askId === "string" ? b.askId : answer.askId,
|
|
444
|
+
status: b.status === "resolved" || b.status === "declined" || b.status === "cancelled" ? b.status : answer.status,
|
|
445
|
+
...(typeof b.mode === "string" ? { mode: b.mode } : {}),
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const env = (parsed ?? {});
|
|
450
|
+
return {
|
|
451
|
+
ok: false,
|
|
452
|
+
status: res.status,
|
|
453
|
+
code: typeof env.code === "string" ? env.code : null,
|
|
454
|
+
message: typeof env.message === "string" ? env.message : `hitl-answer failed (${res.status})`,
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/agent-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@silverprotocol/core": "0.5.0",
|
|
38
|
-
"@guuey/mcp-apps-host": "0.
|
|
38
|
+
"@guuey/mcp-apps-host": "0.8.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": ">=18"
|
package/src/error-codes.ts
CHANGED
|
@@ -53,6 +53,12 @@ export const AGENT_ERROR_CODES = {
|
|
|
53
53
|
DRAINING: "DRAINING",
|
|
54
54
|
/** Refused for this caller — e.g. the link-prompt dismiss route's byo-only rule. */
|
|
55
55
|
FORBIDDEN: "FORBIDDEN",
|
|
56
|
+
/**
|
|
57
|
+
* The consent-answer door's deny==miss (guuey#207): the `AgHitlAnswer` names
|
|
58
|
+
* an ask this pod did not mint (wrong app, a thread the caller does not
|
|
59
|
+
* own, or a pod that takes no profile consent). Never an existence oracle.
|
|
60
|
+
*/
|
|
61
|
+
NOT_FOUND: "NOT_FOUND",
|
|
56
62
|
/** The turn ran past the pod's wall-clock budget. */
|
|
57
63
|
TIMEOUT: "TIMEOUT",
|
|
58
64
|
/** A guuey-side dependency failed (not the agent's own code). */
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ export {
|
|
|
3
3
|
extractAssistantText,
|
|
4
4
|
reduceAssistantText,
|
|
5
5
|
stringField,
|
|
6
|
-
parseConsentRequest,
|
|
7
6
|
parseLinkRequest,
|
|
8
7
|
type ParsedSseEvent,
|
|
9
8
|
} from "./sse.js";
|
|
@@ -12,6 +11,9 @@ export { dismissLinkPrompt } from "./link-prompt.js";
|
|
|
12
11
|
// wraps, for hosts that drive their own turn state machine (guuey#186 G5).
|
|
13
12
|
export { invokeTurn, toInvokeUrl, type InvokeTurnEvent } from "./invoke-turn.js";
|
|
14
13
|
export {
|
|
14
|
+
createHitlAnswerRelay,
|
|
15
|
+
type CreateHitlAnswerRelayOptions,
|
|
16
|
+
type HitlAnswerRelayResult,
|
|
15
17
|
createUiActionRelay,
|
|
16
18
|
type CreateUiActionRelayOptions,
|
|
17
19
|
createUiResourceReader,
|
|
@@ -74,11 +76,20 @@ export { sortHistoryCards, toolNameFor } from "./history.js";
|
|
|
74
76
|
// host folding `invokeTurn`'s agEvents outside the hook builds its transcript
|
|
75
77
|
// on the same terms (the types alone forced the direct dep back, guuey#186 G4).
|
|
76
78
|
export { Reducer } from "@silverprotocol/core";
|
|
77
|
-
export type {
|
|
79
|
+
export type {
|
|
80
|
+
AgEvent,
|
|
81
|
+
AgReduceResult,
|
|
82
|
+
AgMessage,
|
|
83
|
+
AgBlock,
|
|
84
|
+
// The client→pod capability advertisement + the HITL answer the relay
|
|
85
|
+
// delivers (guuey#207) — re-exported so a host names them without a direct
|
|
86
|
+
// `@silverprotocol/core` import.
|
|
87
|
+
AgClientCapabilities,
|
|
88
|
+
AgHitlAnswer,
|
|
89
|
+
} from "@silverprotocol/core";
|
|
78
90
|
export type {
|
|
79
91
|
AgentMessage,
|
|
80
92
|
HistoryCard,
|
|
81
|
-
ProfileConsentRequest,
|
|
82
93
|
ProfileLinkRequest,
|
|
83
94
|
ThreadIdStore,
|
|
84
95
|
GenerateId,
|
package/src/invoke-turn.ts
CHANGED
|
@@ -23,21 +23,9 @@
|
|
|
23
23
|
* fall-through, so new wire events are additive for every consumer.
|
|
24
24
|
*/
|
|
25
25
|
import type { AgEvent } from "@silverprotocol/core";
|
|
26
|
-
import {
|
|
27
|
-
parseConsentRequest,
|
|
28
|
-
parseLinkRequest,
|
|
29
|
-
parseSseEvents,
|
|
30
|
-
reduceAssistantText,
|
|
31
|
-
stringField,
|
|
32
|
-
} from "./sse.js";
|
|
26
|
+
import { parseLinkRequest, parseSseEvents, reduceAssistantText, stringField } from "./sse.js";
|
|
33
27
|
import { ingestMessageFrame } from "./blocks.js";
|
|
34
|
-
import type {
|
|
35
|
-
AgentInvokeStatus,
|
|
36
|
-
InvokeRequest,
|
|
37
|
-
InvokeTransport,
|
|
38
|
-
ProfileConsentRequest,
|
|
39
|
-
ProfileLinkRequest,
|
|
40
|
-
} from "./types.js";
|
|
28
|
+
import type { AgentInvokeStatus, InvokeRequest, InvokeTransport, ProfileLinkRequest } from "./types.js";
|
|
41
29
|
|
|
42
30
|
/**
|
|
43
31
|
* One semantic step of a turn. Field conventions:
|
|
@@ -63,7 +51,6 @@ export type InvokeTurnEvent =
|
|
|
63
51
|
agEvents: AgEvent[];
|
|
64
52
|
}
|
|
65
53
|
| { kind: "error"; message: string; code: string | null }
|
|
66
|
-
| { kind: "profile-consent"; request: ProfileConsentRequest }
|
|
67
54
|
| { kind: "profile-link"; request: ProfileLinkRequest }
|
|
68
55
|
| { kind: "done"; stopReason: string | null };
|
|
69
56
|
|
|
@@ -164,15 +151,12 @@ export async function* invokeTurn(
|
|
|
164
151
|
message: stringField(ev.data, "message") ?? "agent error",
|
|
165
152
|
code: stringField(ev.data, "code") ?? null,
|
|
166
153
|
};
|
|
167
|
-
} else if (ev.event === "profile-consent-needed") {
|
|
168
|
-
// Cross-app profile consent ask (T6). Only a well-formed payload
|
|
169
|
-
// yields; a malformed one is dropped, leaving any prior valid
|
|
170
|
-
// request untouched (never clobbered to null).
|
|
171
|
-
const parsed = parseConsentRequest(ev.data);
|
|
172
|
-
if (parsed) yield { kind: "profile-consent", request: parsed };
|
|
173
154
|
} else if (ev.event === "profile-link-needed") {
|
|
174
155
|
// Cross-app profile LINK invite (linkcoh T3) for an unlinked byo
|
|
175
|
-
// caller.
|
|
156
|
+
// caller. Only a well-formed payload yields; a malformed one is
|
|
157
|
+
// dropped, leaving any prior valid request untouched (never clobbered
|
|
158
|
+
// to null). (Consent is NOT a bespoke event: it rides the AgJSON fold
|
|
159
|
+
// as `hitl.ask` + `turn.done outcome:"paused"` — guuey#207.)
|
|
176
160
|
const parsed = parseLinkRequest(ev.data);
|
|
177
161
|
if (parsed) yield { kind: "profile-link", request: parsed };
|
|
178
162
|
} else if (ev.event === "done") {
|
package/src/react.ts
CHANGED
|
@@ -12,6 +12,7 @@ export {
|
|
|
12
12
|
type HistoryApplication,
|
|
13
13
|
stallProbeDecision,
|
|
14
14
|
STALL_RECOVERY_DEFAULTS,
|
|
15
|
+
DEFAULT_BLOCK_PRESERVING_CAPABILITIES,
|
|
15
16
|
} from "./useAgentInvoke.js";
|
|
16
17
|
// The block-preserving transcript surfaces `AgReduceResult`; re-export it (and
|
|
17
18
|
// `AgEvent`) here so `./react` consumers can type `reduceResult` without a
|
package/src/sse.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* verbatim across web (Studio) and React-Native (Portal).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { ProfileLinkRequest } from "./types.js";
|
|
8
8
|
|
|
9
9
|
export interface ParsedSseEvent {
|
|
10
10
|
event: string;
|
|
@@ -140,30 +140,13 @@ export function stringField(data: unknown, key: string): string | undefined {
|
|
|
140
140
|
return typeof v === "string" ? v : undefined;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
/**
|
|
144
|
-
* Parse a `profile-consent-needed` SSE payload into a typed
|
|
145
|
-
* {@link ProfileConsentRequest}, or `null` if it does not conform. `appId`
|
|
146
|
-
* must be a non-empty string and `requested` exactly `"read"` or
|
|
147
|
-
* `"read-write"`; extra keys are tolerated (ignored). Returns a fresh
|
|
148
|
-
* normalized object so callers get exactly the typed shape, never the raw
|
|
149
|
-
* wire payload with unknown extras.
|
|
150
|
-
*/
|
|
151
|
-
export function parseConsentRequest(data: unknown): ProfileConsentRequest | null {
|
|
152
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
|
|
153
|
-
const appId = (data as { appId?: unknown }).appId;
|
|
154
|
-
const requested = (data as { requested?: unknown }).requested;
|
|
155
|
-
if (typeof appId !== "string" || appId.length === 0) return null;
|
|
156
|
-
if (requested !== "read" && requested !== "read-write") return null;
|
|
157
|
-
return { appId, requested };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
143
|
/**
|
|
161
144
|
* Parse a `profile-link-needed` SSE payload into a typed
|
|
162
|
-
* {@link ProfileLinkRequest}, or `null` if it does not conform.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
145
|
+
* {@link ProfileLinkRequest}, or `null` if it does not conform. `appId` must
|
|
146
|
+
* be a non-empty string and `requested` exactly `"read"` or `"read-write"`;
|
|
147
|
+
* extra keys are tolerated (ignored). Returns a fresh normalized object so
|
|
148
|
+
* callers get exactly the typed shape, never the raw wire payload with
|
|
149
|
+
* unknown extras.
|
|
167
150
|
*/
|
|
168
151
|
export function parseLinkRequest(data: unknown): ProfileLinkRequest | null {
|
|
169
152
|
if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
|
package/src/types.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `MessageStorageAdapter` injection pattern.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
16
|
+
import type { AgClientCapabilities, AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
17
17
|
|
|
18
18
|
/** A flat chat turn as rendered by the consumer UI. */
|
|
19
19
|
export interface AgentMessage {
|
|
@@ -28,20 +28,6 @@ export interface AgentMessage {
|
|
|
28
28
|
clientMessageId?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* A cross-app profile consent request surfaced mid-stream by the pod's
|
|
33
|
-
* `profile-consent-needed` SSE event (nocode-runtime T6). Emitted when the
|
|
34
|
-
* agent declares a profile intent the caller has NOT yet granted for this app,
|
|
35
|
-
* so the consumer UI can prompt the user to authorize `read` or `read-write`
|
|
36
|
-
* access. `requested` mirrors the pod's `ProfileAccess` posture verbatim; the
|
|
37
|
-
* literal union is inlined rather than imported to keep this client SDK free of
|
|
38
|
-
* any backend-package dependency.
|
|
39
|
-
*/
|
|
40
|
-
export interface ProfileConsentRequest {
|
|
41
|
-
appId: string;
|
|
42
|
-
requested: "read" | "read-write";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
31
|
/**
|
|
46
32
|
* A cross-app profile LINK invite surfaced mid-stream by the pod's
|
|
47
33
|
* `profile-link-needed` SSE event (nocode-runtime linkcoh T3). Emitted when an
|
|
@@ -49,8 +35,10 @@ export interface ProfileConsentRequest {
|
|
|
49
35
|
* link their guuey account (via the named `/link` ceremony) so they earn the
|
|
50
36
|
* guuey-wide cross-app profile. `requested` mirrors the pod's `ProfileAccess`
|
|
51
37
|
* posture verbatim (the builder's declared access, not a live ask) — the
|
|
52
|
-
* literal union is inlined rather than imported
|
|
53
|
-
*
|
|
38
|
+
* literal union is inlined rather than imported to keep this client SDK free
|
|
39
|
+
* of any backend-package dependency. (Consent itself is no longer a bespoke
|
|
40
|
+
* event: it rides AgJSON `hitl.ask` + `turn.done outcome:"paused"` in the
|
|
41
|
+
* fold and is answered through `createHitlAnswerRelay` — guuey#207.)
|
|
54
42
|
*/
|
|
55
43
|
export interface ProfileLinkRequest {
|
|
56
44
|
appId: string;
|
|
@@ -87,7 +75,7 @@ export type GenerateId = () => string;
|
|
|
87
75
|
export interface InvokeRequest {
|
|
88
76
|
/** Fully-resolved POST target (already normalised to end in `/agent/invoke`). */
|
|
89
77
|
url: string;
|
|
90
|
-
/** JSON request body: `{ input, threadId?, clientMessageId }`. */
|
|
78
|
+
/** JSON request body: `{ input, threadId?, clientMessageId, capabilities? }`. */
|
|
91
79
|
body: unknown;
|
|
92
80
|
/** Aborts the in-flight stream. */
|
|
93
81
|
signal: AbortSignal;
|
|
@@ -165,6 +153,18 @@ export interface UseAgentInvokeOptions {
|
|
|
165
153
|
* reducer is never constructed and the text behaviour is byte-identical.
|
|
166
154
|
*/
|
|
167
155
|
preserveBlocks?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* The AgJSON client capabilities advertised on every invoke body (spec §3
|
|
158
|
+
* `AgClientCapabilities`, guuey#207). The pod reads them to decide what it
|
|
159
|
+
* may ask: today it declares consent `grantModes` (the three-mode profile
|
|
160
|
+
* grant the `@guuey/chat` card renders) ONLY to a client that advertised
|
|
161
|
+
* `hitl.grantModes` — a client that cannot render the decision surface is
|
|
162
|
+
* simply not asked. Default: when {@link preserveBlocks} is on,
|
|
163
|
+
* `{ hitl: { ask: true, grantModes: true } }` (a block-preserving consumer
|
|
164
|
+
* folds the paused turn the ask rides on — the kit's exact path); when off,
|
|
165
|
+
* nothing is advertised. Pass an explicit object to override either way.
|
|
166
|
+
*/
|
|
167
|
+
capabilities?: AgClientCapabilities;
|
|
168
168
|
/**
|
|
169
169
|
* Stall recovery for a half-dead stream (guuey#192). A connection that dies
|
|
170
170
|
* WITHOUT erroring (TCP alive, zero bytes, no `done`) would otherwise leave
|
|
@@ -283,26 +283,16 @@ export interface UseAgentInvokeReturn {
|
|
|
283
283
|
* `reset()` clears it back to `[]`.
|
|
284
284
|
*/
|
|
285
285
|
historyCards: HistoryCard[];
|
|
286
|
-
/**
|
|
287
|
-
* The latest cross-app profile consent request the pod asked for on THIS
|
|
288
|
-
* conversation, or `null`. Set from a well-formed `profile-consent-needed`
|
|
289
|
-
* SSE event (see {@link ProfileConsentRequest}); malformed payloads are
|
|
290
|
-
* dropped and leave the field untouched. `reset()` and an app switch clear
|
|
291
|
-
* it back to `null`. Consumers that never render a consent prompt (e.g.
|
|
292
|
-
* Studio) simply ignore this field.
|
|
293
|
-
*/
|
|
294
|
-
profileConsentRequest: ProfileConsentRequest | null;
|
|
295
|
-
/** Dismiss the pending {@link profileConsentRequest} (back to `null`). */
|
|
296
|
-
clearProfileConsentRequest: () => void;
|
|
297
286
|
/**
|
|
298
287
|
* The latest cross-app profile LINK invite the pod asked for on THIS
|
|
299
288
|
* conversation, or `null`. Set from a well-formed `profile-link-needed`
|
|
300
289
|
* SSE event (see {@link ProfileLinkRequest}); malformed payloads are
|
|
301
290
|
* dropped and leave the field untouched. `reset()` and an app switch clear
|
|
302
291
|
* it back to `null`. Consumers that never render a link prompt simply
|
|
303
|
-
* ignore this field. Distinct from
|
|
304
|
-
* invites an UNLINKED byo user to link their
|
|
305
|
-
* already-linked user to grant an app
|
|
292
|
+
* ignore this field. Distinct from consent (an AgJSON `hitl.ask` in the
|
|
293
|
+
* fold, guuey#207): this one invites an UNLINKED byo user to link their
|
|
294
|
+
* account; consent asks an already-linked user to grant an app
|
|
295
|
+
* read/read-write access.
|
|
306
296
|
*/
|
|
307
297
|
profileLinkRequest: ProfileLinkRequest | null;
|
|
308
298
|
/** Dismiss the pending {@link profileLinkRequest} (back to `null`). */
|
package/src/useAgentInvoke.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ggui generative-UI protocol that `@ggui-ai/mcp-apps-react`'s useInvoke targets):
|
|
6
6
|
*
|
|
7
7
|
* POST {endpointUrl}/agent/invoke
|
|
8
|
-
* body: { input, threadId?, clientMessageId }
|
|
8
|
+
* body: { input, threadId?, clientMessageId, capabilities? }
|
|
9
9
|
* ← SSE:
|
|
10
10
|
* event: session { sessionId, userId, threadId? }
|
|
11
11
|
* event: message <SDKMessage JSON> (assistant turns + result)
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* `./web-adapters` for the web (Studio) bundle; Portal supplies RN adapters.
|
|
23
23
|
*/
|
|
24
24
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
25
|
-
import { Reducer, type AgReduceResult } from "@silverprotocol/core";
|
|
25
|
+
import { Reducer, type AgClientCapabilities, type AgReduceResult } from "@silverprotocol/core";
|
|
26
26
|
import { invokeTurn, toInvokeUrl } from "./invoke-turn.js";
|
|
27
27
|
import { AgentResponseError } from "./errors.js";
|
|
28
28
|
import { withActivityObserver } from "./transport.js";
|
|
@@ -33,7 +33,6 @@ import type {
|
|
|
33
33
|
AgentMessage,
|
|
34
34
|
HistoryCard,
|
|
35
35
|
HistoryLoadResult,
|
|
36
|
-
ProfileConsentRequest,
|
|
37
36
|
ProfileLinkRequest,
|
|
38
37
|
StallRecoveryOptions,
|
|
39
38
|
UseAgentInvokeOptions,
|
|
@@ -44,6 +43,16 @@ function threadStorageKey(appId: string | undefined): string {
|
|
|
44
43
|
return `guuey:thread:${appId ?? "default"}`;
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
/**
|
|
47
|
+
* What a block-preserving consumer advertises by default (guuey#207): it
|
|
48
|
+
* folds `turn.done outcome:"paused"` records, so it can render the AgJSON
|
|
49
|
+
* hitl card with declared grant modes — the `@guuey/chat` path. See
|
|
50
|
+
* `UseAgentInvokeOptions.capabilities`.
|
|
51
|
+
*/
|
|
52
|
+
export const DEFAULT_BLOCK_PRESERVING_CAPABILITIES: AgClientCapabilities = {
|
|
53
|
+
hitl: { ask: true, grantModes: true },
|
|
54
|
+
};
|
|
55
|
+
|
|
47
56
|
/** The decision `applyHistoryResult` reaches for a loaded transcript. */
|
|
48
57
|
export type HistoryApplication =
|
|
49
58
|
| { kind: "seed"; messages: AgentMessage[] }
|
|
@@ -136,14 +145,11 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
136
145
|
// contract). Independent of the live `reduceResult` fold — populated only
|
|
137
146
|
// when a card-carrying history load seeds the transcript.
|
|
138
147
|
const [historyCards, setHistoryCards] = useState<HistoryCard[]>([]);
|
|
139
|
-
// The pod's latest cross-app profile consent ask on this conversation (T6's
|
|
140
|
-
// `profile-consent-needed` SSE event), or null. Cleared on app switch /
|
|
141
|
-
// reset / explicit dismiss. Consumers with no consent UI just ignore it.
|
|
142
|
-
const [profileConsentRequest, setProfileConsentRequest] = useState<ProfileConsentRequest | null>(null);
|
|
143
148
|
// The pod's latest cross-app profile LINK invite on this conversation (T3's
|
|
144
149
|
// `profile-link-needed` SSE event), or null. Cleared on app switch / reset /
|
|
145
|
-
// explicit dismiss
|
|
146
|
-
//
|
|
150
|
+
// explicit dismiss. (Consent is NOT hook state: it rides the AgJSON fold as
|
|
151
|
+
// a paused turn — `reduceResult` — and is answered through
|
|
152
|
+
// `createHitlAnswerRelay`, guuey#207.)
|
|
147
153
|
const [profileLinkRequest, setProfileLinkRequest] = useState<ProfileLinkRequest | null>(null);
|
|
148
154
|
// The last turn's ending posture + the optimistic-send ledger — the
|
|
149
155
|
// transcript renderer's inputs (guuey#135 wave 3b; see the return-type
|
|
@@ -201,8 +207,7 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
201
207
|
reducerRef.current = null;
|
|
202
208
|
setReduceResult(null);
|
|
203
209
|
setHistoryCards([]);
|
|
204
|
-
// A prior app's
|
|
205
|
-
setProfileConsentRequest(null);
|
|
210
|
+
// A prior app's link invite must never leak into the new conversation.
|
|
206
211
|
setProfileLinkRequest(null);
|
|
207
212
|
setAborted(false);
|
|
208
213
|
setAdopted(false);
|
|
@@ -295,17 +300,12 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
295
300
|
reducerRef.current = null;
|
|
296
301
|
setReduceResult(null);
|
|
297
302
|
setHistoryCards([]);
|
|
298
|
-
setProfileConsentRequest(null);
|
|
299
303
|
setProfileLinkRequest(null);
|
|
300
304
|
setAborted(false);
|
|
301
305
|
setAdopted(false);
|
|
302
306
|
setSendStates({});
|
|
303
307
|
}, [appId]);
|
|
304
308
|
|
|
305
|
-
const clearProfileConsentRequest = useCallback(() => {
|
|
306
|
-
setProfileConsentRequest(null);
|
|
307
|
-
}, []);
|
|
308
|
-
|
|
309
309
|
const clearProfileLinkRequest = useCallback(() => {
|
|
310
310
|
setProfileLinkRequest(null);
|
|
311
311
|
}, []);
|
|
@@ -474,10 +474,17 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
474
474
|
|
|
475
475
|
try {
|
|
476
476
|
const invokeUrl = toInvokeUrl(endpointUrl);
|
|
477
|
+
// The advertised AgJSON client capabilities (spec §3, guuey#207): an
|
|
478
|
+
// explicit option wins; else a block-preserving consumer advertises
|
|
479
|
+
// the hitl grant-mode card it can render, and a text-only one nothing.
|
|
480
|
+
const capabilities =
|
|
481
|
+
opts.capabilities ??
|
|
482
|
+
(preserveBlocksRef.current ? DEFAULT_BLOCK_PRESERVING_CAPABILITIES : undefined);
|
|
477
483
|
const body = {
|
|
478
484
|
input,
|
|
479
485
|
...(threadIdRef.current ? { threadId: threadIdRef.current } : {}),
|
|
480
486
|
clientMessageId,
|
|
487
|
+
...(capabilities !== undefined ? { capabilities } : {}),
|
|
481
488
|
};
|
|
482
489
|
|
|
483
490
|
// The wire walk lives in `invokeTurn` (the pure per-turn generator —
|
|
@@ -519,8 +526,6 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
519
526
|
// a previous turn's code standing).
|
|
520
527
|
setError(ev.message);
|
|
521
528
|
setErrorCode(ev.code);
|
|
522
|
-
} else if (ev.kind === "profile-consent") {
|
|
523
|
-
setProfileConsentRequest(ev.request);
|
|
524
529
|
} else if (ev.kind === "profile-link") {
|
|
525
530
|
setProfileLinkRequest(ev.request);
|
|
526
531
|
}
|
|
@@ -588,8 +593,6 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
|
|
|
588
593
|
reset,
|
|
589
594
|
reduceResult,
|
|
590
595
|
historyCards,
|
|
591
|
-
profileConsentRequest,
|
|
592
|
-
clearProfileConsentRequest,
|
|
593
596
|
profileLinkRequest,
|
|
594
597
|
clearProfileLinkRequest,
|
|
595
598
|
aborted,
|
package/src/web-adapters.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ResolvedViewMount,
|
|
16
16
|
type UiActionRequest,
|
|
17
17
|
} from "@guuey/mcp-apps-host";
|
|
18
|
+
import type { AgHitlAnswer } from "@silverprotocol/core";
|
|
18
19
|
import type { AgentInvokeAdapters, InvokeTransport, ThreadIdStore } from "./types.js";
|
|
19
20
|
import { fetchThreadHistory, HistoryUnauthorizedError } from "./history.js";
|
|
20
21
|
import { fetchStreamTransport, sendableGuestSecret, GUEST_HEADER } from "./transport.js";
|
|
@@ -526,3 +527,114 @@ export function createUiActionRelay(
|
|
|
526
527
|
};
|
|
527
528
|
return createMcpUiActionRelay({ callTool });
|
|
528
529
|
}
|
|
530
|
+
|
|
531
|
+
/** `<pod base>/agent/hitl-answer` — the AgJSON HITL answer door (guuey#207). */
|
|
532
|
+
function toHitlAnswerUrl(endpointUrl: string): string {
|
|
533
|
+
return toInvokeUrl(endpointUrl).replace(/\/agent\/invoke$/, "/agent/hitl-answer");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Options for {@link createHitlAnswerRelay} — the same credential surface as the card relays. */
|
|
537
|
+
export interface CreateHitlAnswerRelayOptions {
|
|
538
|
+
/** The surface's invoke endpoint (pod base URL or full `/agent/invoke` URL) — the answer door lives on the pod. */
|
|
539
|
+
endpointUrl: string;
|
|
540
|
+
/** Signed-in bearer — wins over the guest secret (same rule as the transport). */
|
|
541
|
+
getAccessToken?: (opts?: { forceRefresh?: boolean }) => Promise<string | null>;
|
|
542
|
+
/** Caller-owned anonymous guest secret (widget / guest chat). */
|
|
543
|
+
guestSecret?: string | null;
|
|
544
|
+
/** Injectable for tests. */
|
|
545
|
+
fetchImpl?: typeof fetch;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* The pod's answer to a delivered {@link AgHitlAnswer}. `ok` carries the
|
|
550
|
+
* body the door returns (`askId`, echoed `status`, and — for a recorded
|
|
551
|
+
* consent — the grant `mode` written); every non-2xx collapses to the pod's
|
|
552
|
+
* `{ code, message }` envelope (the same vocabulary as `AGENT_ERROR_CODES`,
|
|
553
|
+
* e.g. `NOT_FOUND` for an ask this pod did not mint, `INVALID_REQUEST` for a
|
|
554
|
+
* spec-invalid answer) with the HTTP status; a transport failure is
|
|
555
|
+
* `status: 0` with a null code.
|
|
556
|
+
*/
|
|
557
|
+
export type HitlAnswerRelayResult =
|
|
558
|
+
| { ok: true; body: { askId: string; status: AgHitlAnswer["status"]; mode?: string } }
|
|
559
|
+
| { ok: false; status: number; code: string | null; message: string };
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Build the client→pod channel for AgJSON HITL answers (guuey#207): `POST
|
|
563
|
+
* <pod>/agent/hitl-answer` with the spec {@link AgHitlAnswer} the kit's
|
|
564
|
+
* `answerHitlPrompt` constructed (already validated against the ask's
|
|
565
|
+
* persisted declaration). The pod owns EVERYTHING trust-shaped — caller
|
|
566
|
+
* identity (the same three families as the invoke), which ask it minted,
|
|
567
|
+
* the thread a `once` grant binds to, the access level written — this
|
|
568
|
+
* transport only carries the surface's existing credential under the
|
|
569
|
+
* one-carrier rule (bearer → guest header → cookie), with the card relays'
|
|
570
|
+
* single 401 forceRefresh retry.
|
|
571
|
+
*
|
|
572
|
+
* Today the only producer is the pod's cross-app profile consent ask (the
|
|
573
|
+
* three-mode grant), whose answer resolves into the caller's own
|
|
574
|
+
* `ProfileGrant` row; the channel is generic by construction — any future
|
|
575
|
+
* `hitl.ask` the runtime emits is answered through this same door.
|
|
576
|
+
*/
|
|
577
|
+
export function createHitlAnswerRelay(
|
|
578
|
+
options: CreateHitlAnswerRelayOptions,
|
|
579
|
+
): (answer: AgHitlAnswer) => Promise<HitlAnswerRelayResult> {
|
|
580
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
581
|
+
const url = toHitlAnswerUrl(options.endpointUrl);
|
|
582
|
+
return async (answer) => {
|
|
583
|
+
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
584
|
+
const body = JSON.stringify(answer);
|
|
585
|
+
const init: RequestInit = { method: "POST", headers, body };
|
|
586
|
+
const token = options.getAccessToken ? await options.getAccessToken() : null;
|
|
587
|
+
const guest = sendableGuestSecret(options.guestSecret);
|
|
588
|
+
if (token) {
|
|
589
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
590
|
+
} else if (guest) {
|
|
591
|
+
headers[GUEST_HEADER] = guest;
|
|
592
|
+
} else {
|
|
593
|
+
init.credentials = "include";
|
|
594
|
+
}
|
|
595
|
+
let res: Response;
|
|
596
|
+
try {
|
|
597
|
+
res = await fetchImpl(url, init);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
return { ok: false, status: 0, code: null, message: err instanceof Error ? err.message : String(err) };
|
|
600
|
+
}
|
|
601
|
+
if (res.status === 401 && options.getAccessToken) {
|
|
602
|
+
const fresh = await options.getAccessToken({ forceRefresh: true }).catch(() => null);
|
|
603
|
+
if (fresh) {
|
|
604
|
+
try {
|
|
605
|
+
res = await fetchImpl(url, {
|
|
606
|
+
method: "POST",
|
|
607
|
+
headers: { ...headers, authorization: `Bearer ${fresh}` },
|
|
608
|
+
body,
|
|
609
|
+
});
|
|
610
|
+
} catch (err) {
|
|
611
|
+
return { ok: false, status: 0, code: null, message: err instanceof Error ? err.message : String(err) };
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
let parsed: unknown = undefined;
|
|
616
|
+
try {
|
|
617
|
+
parsed = await res.json();
|
|
618
|
+
} catch {
|
|
619
|
+
parsed = undefined;
|
|
620
|
+
}
|
|
621
|
+
if (res.ok) {
|
|
622
|
+
const b = (parsed ?? {}) as { askId?: unknown; status?: unknown; mode?: unknown };
|
|
623
|
+
return {
|
|
624
|
+
ok: true,
|
|
625
|
+
body: {
|
|
626
|
+
askId: typeof b.askId === "string" ? b.askId : answer.askId,
|
|
627
|
+
status: b.status === "resolved" || b.status === "declined" || b.status === "cancelled" ? b.status : answer.status,
|
|
628
|
+
...(typeof b.mode === "string" ? { mode: b.mode } : {}),
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
const env = (parsed ?? {}) as { code?: unknown; message?: unknown };
|
|
633
|
+
return {
|
|
634
|
+
ok: false,
|
|
635
|
+
status: res.status,
|
|
636
|
+
code: typeof env.code === "string" ? env.code : null,
|
|
637
|
+
message: typeof env.message === "string" ? env.message : `hitl-answer failed (${res.status})`,
|
|
638
|
+
};
|
|
639
|
+
};
|
|
640
|
+
}
|