@guuey/agent-client 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -7
- package/dist/error-codes.d.ts +87 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +82 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/history.d.ts +1 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/index.d.ts +13 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -6
- package/dist/invoke-turn.d.ts +101 -0
- package/dist/invoke-turn.d.ts.map +1 -0
- package/dist/invoke-turn.js +124 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/dist/saturation-retry.d.ts +101 -0
- package/dist/saturation-retry.d.ts.map +1 -0
- package/dist/saturation-retry.js +207 -0
- package/dist/sse.d.ts +1 -1
- package/dist/sse.d.ts.map +1 -1
- package/dist/transport.d.ts +90 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +189 -0
- package/dist/types.d.ts +101 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/useAgentInvoke.d.ts +29 -1
- package/dist/useAgentInvoke.d.ts.map +1 -1
- package/dist/useAgentInvoke.js +297 -86
- package/dist/web-adapters.d.ts +30 -34
- package/dist/web-adapters.d.ts.map +1 -1
- package/dist/web-adapters.js +72 -123
- package/package.json +11 -5
- package/src/error-codes.ts +89 -0
- package/src/errors.ts +35 -0
- package/src/history.ts +1 -1
- package/src/index.ts +50 -10
- package/src/invoke-turn.ts +187 -0
- package/src/react.ts +7 -1
- package/src/saturation-retry.ts +247 -0
- package/src/sse.ts +1 -1
- package/src/transport.ts +260 -0
- package/src/types.ts +102 -3
- package/src/useAgentInvoke.ts +288 -86
- package/src/web-adapters.ts +92 -134
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { AgentResponseError } from "./errors.js";
|
|
2
|
+
import { parseRetryAfterSeconds, withColdStartRetry, withSaturationRetry, } from "./saturation-retry.js";
|
|
3
|
+
/**
|
|
4
|
+
* Header carrying a caller-owned anonymous guest secret. A LOCAL MIRROR of the
|
|
5
|
+
* two server-side constants — the pod's `GUEST_HEADER_NAME`
|
|
6
|
+
* (`backend/services/nocode-runtime/src/identity.ts`) and the read plane's
|
|
7
|
+
* `GUEST_HEADER` (`backend/amplify/functions/publicApi/identity.ts`) — because
|
|
8
|
+
* this is a published npm package and cannot take a `@guuey-private` dep (same
|
|
9
|
+
* arrangement as `@guuey/host`'s mirrored fs-contract constants). The string is
|
|
10
|
+
* a wire contract: both planes already advertise it in
|
|
11
|
+
* `Access-Control-Allow-Headers`, so changing it is a breaking protocol change,
|
|
12
|
+
* not a rename.
|
|
13
|
+
*/
|
|
14
|
+
export const GUEST_HEADER = "x-guuey-guest";
|
|
15
|
+
/**
|
|
16
|
+
* A well-formed guest secret: exactly 32 bytes as 64 LOWERCASE hex chars —
|
|
17
|
+
* the shape `crypto.getRandomValues` + hex-encoding mints.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately stricter than the server's `/^[a-f0-9]{64}$/i` (pod
|
|
20
|
+
* `identity.ts`, publicApi `identity.ts`): both sides lowercase before
|
|
21
|
+
* hashing, so an uppercase secret would in fact be accepted, but the only
|
|
22
|
+
* supported mint path emits lowercase and a non-canonical value means the
|
|
23
|
+
* caller's storage is not what this adapter expects. Anything that fails is
|
|
24
|
+
* IGNORED — the request falls through to cookie mode rather than sending a
|
|
25
|
+
* secret the two identity planes might key differently.
|
|
26
|
+
*/
|
|
27
|
+
const GUEST_SECRET_RE = /^[0-9a-f]{64}$/;
|
|
28
|
+
/**
|
|
29
|
+
* Narrow a caller-supplied guest secret to a value that is safe to put on the
|
|
30
|
+
* wire, or `null`. The single gate for the header: every write of
|
|
31
|
+
* {@link GUEST_HEADER} in this package goes through it, so a malformed secret
|
|
32
|
+
* can never reach a request. The value is never logged (here or anywhere on
|
|
33
|
+
* this path) — it IS the anonymous identity, so a leak is an impersonation.
|
|
34
|
+
*/
|
|
35
|
+
export function sendableGuestSecret(secret) {
|
|
36
|
+
return typeof secret === "string" && GUEST_SECRET_RE.test(secret) ? secret : null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Is this request a cross-origin call from a browser document? Only then can
|
|
40
|
+
* a fetch `TypeError` be a CORS refusal worth hinting about. `location` is
|
|
41
|
+
* read via `typeof` so Node and React Native (where CORS does not exist)
|
|
42
|
+
* answer false; an unparseable URL answers false rather than throwing from
|
|
43
|
+
* inside error handling.
|
|
44
|
+
*/
|
|
45
|
+
function isCrossOriginBrowserCall(url) {
|
|
46
|
+
if (typeof location === "undefined")
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
return new URL(url).origin !== location.origin;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One invoke attempt: opens the request and yields decoded SSE chunks.
|
|
57
|
+
* {@link fetchStreamTransport} wraps this with the shared saturation retry —
|
|
58
|
+
* every behaviour below is per-attempt.
|
|
59
|
+
*
|
|
60
|
+
* Exactly ONE identity carrier per request, in order:
|
|
61
|
+
*
|
|
62
|
+
* 1. `accessToken` → `Authorization: Bearer` — the pod identifies the caller
|
|
63
|
+
* by their verified access token (the same identity the history read
|
|
64
|
+
* plane uses, so persisted threads round-trip on reload).
|
|
65
|
+
* 2. a well-formed `guestSecret` → `x-guuey-guest` — the caller owns and
|
|
66
|
+
* persists its own anonymous secret. The path for hosts with no usable
|
|
67
|
+
* cookie jar: React-Native, and the embedded widget, whose third-party
|
|
68
|
+
* iframe cannot rely on the pod's cookie surviving browser partitioning.
|
|
69
|
+
* The pod never mints a cookie for a header client.
|
|
70
|
+
* 3. neither → `credentials: "include"`, which round-trips the HttpOnly
|
|
71
|
+
* `guuey_guest` cookie the pod mints for anonymous browser callers.
|
|
72
|
+
*
|
|
73
|
+
* Never two at once: a bearer wins over a guest secret, and a request that
|
|
74
|
+
* carries either header does NOT also send cookie credentials.
|
|
75
|
+
*
|
|
76
|
+
* Reads the body via `ReadableStream.getReader()` (browser).
|
|
77
|
+
*/
|
|
78
|
+
async function* streamInvokeOnce(req, accessToken, guestSecret) {
|
|
79
|
+
const headers = {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
Accept: "text/event-stream",
|
|
82
|
+
};
|
|
83
|
+
const init = {
|
|
84
|
+
method: "POST",
|
|
85
|
+
signal: req.signal,
|
|
86
|
+
headers,
|
|
87
|
+
body: JSON.stringify(req.body),
|
|
88
|
+
};
|
|
89
|
+
const guest = sendableGuestSecret(guestSecret);
|
|
90
|
+
if (accessToken) {
|
|
91
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
92
|
+
}
|
|
93
|
+
else if (guest) {
|
|
94
|
+
headers[GUEST_HEADER] = guest;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
init.credentials = "include";
|
|
98
|
+
}
|
|
99
|
+
let resp;
|
|
100
|
+
try {
|
|
101
|
+
resp = await fetch(req.url, init);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// A network-level TypeError on a CROSS-ORIGIN invoke from a browser is,
|
|
105
|
+
// in practice, very often a missing allowedDomains entry — the CORS
|
|
106
|
+
// preflight failed and the web platform deliberately reports nothing
|
|
107
|
+
// more specific (guuey#186 Gap 2: the console.ggui.ai embed lost real
|
|
108
|
+
// time to an unexplained "Failed to fetch"). The error stays a
|
|
109
|
+
// TypeError with the original as `cause`; the added sentence is a HINT,
|
|
110
|
+
// not a diagnosis — offline, DNS and CSP failures throw the same shape.
|
|
111
|
+
// Same-origin calls and non-browser runtimes (no `location`) cannot be
|
|
112
|
+
// CORS refusals, so they pass through untouched.
|
|
113
|
+
if (err instanceof TypeError && isCrossOriginBrowserCall(req.url)) {
|
|
114
|
+
throw new TypeError(`${err.message} — if this is a browser embed, check the app's allowedDomains (the CORS allowlist must include this page's origin)`, { cause: err });
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
if (!resp.ok || !resp.body) {
|
|
119
|
+
// Surface a structured pod error ({ code, message }) when present — e.g. a
|
|
120
|
+
// QUOTA_EXCEEDED 429 carries an upgrade message the UI should show. Fall
|
|
121
|
+
// back to the bare status for non-JSON failures.
|
|
122
|
+
const body = await resp.json().catch(() => null);
|
|
123
|
+
let message = `agent responded ${resp.status}`;
|
|
124
|
+
let code;
|
|
125
|
+
if (body !== null && typeof body === "object") {
|
|
126
|
+
if ("message" in body && typeof body.message === "string" && body.message) {
|
|
127
|
+
message = body.message;
|
|
128
|
+
}
|
|
129
|
+
if ("code" in body && typeof body.code === "string") {
|
|
130
|
+
code = body.code;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
throw new AgentResponseError(message, resp.status, code, parseRetryAfterSeconds(resp.headers.get("Retry-After")));
|
|
134
|
+
}
|
|
135
|
+
const reader = resp.body.getReader();
|
|
136
|
+
const decoder = new TextDecoder();
|
|
137
|
+
for (;;) {
|
|
138
|
+
const { value, done } = await reader.read();
|
|
139
|
+
if (done)
|
|
140
|
+
break;
|
|
141
|
+
yield decoder.decode(value, { stream: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The web SSE transport: {@link streamInvokeOnce} under the shared
|
|
146
|
+
* {@link withSaturationRetry} wrapper, itself under {@link withColdStartRetry}.
|
|
147
|
+
* Every consumer of this transport (Studio, the widget, anything built on
|
|
148
|
+
* `createWebAdapters`) therefore inherits the single `POD_SATURATED` retry AND
|
|
149
|
+
* the bounded cold-start 503 retry, the same pair Portal's React-Native
|
|
150
|
+
* transport wears — see the wrappers' docblocks for which refusals retry,
|
|
151
|
+
* which deliberately do not, and why both retries are invisible to the hook.
|
|
152
|
+
* Both wrappers guard on "nothing yielded yet": once a chunk has streamed,
|
|
153
|
+
* NOTHING re-POSTs.
|
|
154
|
+
*/
|
|
155
|
+
export function fetchStreamTransport(req, accessToken, guestSecret, options = {}) {
|
|
156
|
+
const { getBearer } = options;
|
|
157
|
+
const once = async function* (attempt) {
|
|
158
|
+
// Per-attempt resolution: each retry re-asks the provider (fresh token
|
|
159
|
+
// after a backoff wait) instead of replaying a captured one.
|
|
160
|
+
const bearer = getBearer ? await getBearer() : accessToken;
|
|
161
|
+
yield* streamInvokeOnce(attempt, bearer, guestSecret);
|
|
162
|
+
};
|
|
163
|
+
const saturated = withSaturationRetry(once, { sleep: options.sleep });
|
|
164
|
+
if (options.coldStartRetry === false)
|
|
165
|
+
return saturated(req);
|
|
166
|
+
return withColdStartRetry(saturated, {
|
|
167
|
+
sleep: options.sleep,
|
|
168
|
+
...options.coldStartRetry,
|
|
169
|
+
})(req);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Wrap a transport so every yielded chunk ALSO pings `onChunk` — the
|
|
173
|
+
* byte-level liveness signal `useAgentInvoke`'s stall watchdog runs on
|
|
174
|
+
* (guuey#192). Purely observational: chunks pass through unchanged, errors
|
|
175
|
+
* and completion propagate untouched, and the wrapper adds no timers of its
|
|
176
|
+
* own — the OBSERVER owns the clock, this module only reports activity. The
|
|
177
|
+
* first ping doubles as the "first byte seen" arming signal, which is why
|
|
178
|
+
* the watchdog never fires during a silent cold start: no bytes, no ping,
|
|
179
|
+
* no armed timer (that phase belongs to {@link withColdStartRetry} and the
|
|
180
|
+
* user's own abort).
|
|
181
|
+
*/
|
|
182
|
+
export function withActivityObserver(transport, onChunk) {
|
|
183
|
+
return async function* observed(req) {
|
|
184
|
+
for await (const chunk of transport(req)) {
|
|
185
|
+
onChunk();
|
|
186
|
+
yield chunk;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ import type { AgReduceResult, JsonValue } from "@silverprotocol/core";
|
|
|
17
17
|
export interface AgentMessage {
|
|
18
18
|
role: "user" | "assistant";
|
|
19
19
|
text: string;
|
|
20
|
+
/**
|
|
21
|
+
* The invoke body's `clientMessageId`, present on user turns this client
|
|
22
|
+
* sent live (the optimistic push tags it). History-rehydrated entries and
|
|
23
|
+
* assistant turns omit it. The transcript renderer's R0 send-lifecycle
|
|
24
|
+
* join key (`UseAgentInvokeReturn.sendStates`).
|
|
25
|
+
*/
|
|
26
|
+
clientMessageId?: string;
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* A cross-app profile consent request surfaced mid-stream by the pod's
|
|
@@ -117,6 +124,22 @@ export interface AgentInvokeAdapters {
|
|
|
117
124
|
/** Optional: rehydrate the transcript for a hydrated threadId. See {@link AgentInvokeHistoryAdapter}. */
|
|
118
125
|
history?: AgentInvokeHistoryAdapter;
|
|
119
126
|
}
|
|
127
|
+
/** Tuning for the guuey#192 stall watchdog — see {@link UseAgentInvokeOptions.stallRecovery}. */
|
|
128
|
+
export interface StallRecoveryOptions {
|
|
129
|
+
/**
|
|
130
|
+
* Byte-inactivity window (ms) before a history probe fires. Armed only
|
|
131
|
+
* AFTER the first byte of the turn — pre-first-byte silence is a legitimate
|
|
132
|
+
* cold start and never triggers. Every received chunk resets it.
|
|
133
|
+
* Default 25000.
|
|
134
|
+
*/
|
|
135
|
+
windowMs?: number;
|
|
136
|
+
/**
|
|
137
|
+
* Fruitless probes (history shows the turn still in flight, or no probe is
|
|
138
|
+
* possible) before the turn fails with `CLIENT_ERROR_CODES.STREAM_STALLED`.
|
|
139
|
+
* Any received byte resets the count. Default 4.
|
|
140
|
+
*/
|
|
141
|
+
probeAttempts?: number;
|
|
142
|
+
}
|
|
120
143
|
export interface UseAgentInvokeOptions {
|
|
121
144
|
/** Pod base URL (with or without a trailing `/agent/invoke`). Chat is disabled when null. */
|
|
122
145
|
endpointUrl: string | null;
|
|
@@ -131,6 +154,29 @@ export interface UseAgentInvokeOptions {
|
|
|
131
154
|
* reducer is never constructed and the text behaviour is byte-identical.
|
|
132
155
|
*/
|
|
133
156
|
preserveBlocks?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Stall recovery for a half-dead stream (guuey#192). A connection that dies
|
|
159
|
+
* WITHOUT erroring (TCP alive, zero bytes, no `done`) would otherwise leave
|
|
160
|
+
* the turn suspended forever — a frozen cursor while the backend may have
|
|
161
|
+
* completed and persisted the reply. On by default; `false` restores the
|
|
162
|
+
* old hang-forever behaviour; an object tunes the window/attempts. Armed
|
|
163
|
+
* only after the first byte; probes are non-destructive (they read history
|
|
164
|
+
* WITHOUT touching the live stream) and adopt the finished reply from the
|
|
165
|
+
* history adapter when it is already persisted — see the watchdog notes in
|
|
166
|
+
* `useAgentInvoke`.
|
|
167
|
+
*/
|
|
168
|
+
stallRecovery?: false | StallRecoveryOptions;
|
|
169
|
+
/**
|
|
170
|
+
* Optional external abort authority (guuey#186 Gap 4): a host lifecycle —
|
|
171
|
+
* route change, dialog close, screen unmount — that must be able to stop
|
|
172
|
+
* an in-flight turn without holding the hook's own `abort()`. Composed
|
|
173
|
+
* with (never replacing) the hook's internal per-turn controller: when
|
|
174
|
+
* this signal aborts, the current turn aborts exactly as `abort()` would;
|
|
175
|
+
* the hook's `abort()`/`reset()` keep working regardless. Already-aborted
|
|
176
|
+
* at send time means the send is refused before any request is made. The
|
|
177
|
+
* signal may span many turns — each send observes its CURRENT state.
|
|
178
|
+
*/
|
|
179
|
+
signal?: AbortSignal;
|
|
134
180
|
}
|
|
135
181
|
/**
|
|
136
182
|
* The per-turn lifecycle (guuey#91), derived ENTIRELY from frames the pod
|
|
@@ -148,9 +194,16 @@ export interface UseAgentInvokeOptions {
|
|
|
148
194
|
* - `responding` — assistant text is arriving (`text.start`/`text.delta`
|
|
149
195
|
* silver frames, or bypass text/assistant frames).
|
|
150
196
|
*
|
|
151
|
-
* Failure keeps its own channel ({@link UseAgentInvokeReturn.error}
|
|
152
|
-
* is deliberately no `error`
|
|
153
|
-
* returns to `ready` so the
|
|
197
|
+
* Failure keeps its own channel ({@link UseAgentInvokeReturn.error} +
|
|
198
|
+
* {@link UseAgentInvokeReturn.errorCode}) — there is deliberately no `error`
|
|
199
|
+
* status: after any terminal outcome the status returns to `ready` so the
|
|
200
|
+
* composer re-enables.
|
|
201
|
+
*
|
|
202
|
+
* There is likewise no `retrying` state. `fetchStreamTransport` retries a
|
|
203
|
+
* `POD_SATURATED` refusal once by itself, but that happens before any frame is
|
|
204
|
+
* yielded, so the turn stays in `connecting` for the backoff and the hook never
|
|
205
|
+
* learns it happened. This union describes the POD's turn lifecycle; transport
|
|
206
|
+
* plumbing does not belong in it.
|
|
154
207
|
*/
|
|
155
208
|
export type AgentInvokeStatus = "ready" | "connecting" | "thinking" | "using-tool" | "responding";
|
|
156
209
|
export interface UseAgentInvokeReturn {
|
|
@@ -162,6 +215,27 @@ export interface UseAgentInvokeReturn {
|
|
|
162
215
|
/** The active tool's wire name while `status === 'using-tool'`, else null. */
|
|
163
216
|
activeTool: string | null;
|
|
164
217
|
error: string | null;
|
|
218
|
+
/**
|
|
219
|
+
* The pod's wire code for the failure in {@link error}, when it carried one
|
|
220
|
+
* — `QUOTA_EXCEEDED`, `POD_SATURATED`, `GUEST_ACCESS_DISABLED`, … (see
|
|
221
|
+
* `AGENT_ERROR_CODES`, and branch on those constants rather than re-typing
|
|
222
|
+
* the literals). `null` when there is no error, or when the failure had no
|
|
223
|
+
* code: a network drop, a host-adapter throw, or an `event: error` frame
|
|
224
|
+
* without one.
|
|
225
|
+
*
|
|
226
|
+
* Both failure channels feed it — the pre-stream refusal (thrown as
|
|
227
|
+
* `AgentResponseError`) and the in-band `event: error` frame — because the
|
|
228
|
+
* two carry the SAME vocabulary; a consumer branches once, not per channel.
|
|
229
|
+
* Set and cleared in lockstep with {@link error}: a new `send()`, `reset()`,
|
|
230
|
+
* or an app switch clears both.
|
|
231
|
+
*
|
|
232
|
+
* It is a `string`, not the `AgentErrorCode` union: the pod may ship a new
|
|
233
|
+
* code before a consumer upgrades this SDK, and a narrowed type would make
|
|
234
|
+
* that unrepresentable rather than merely unhandled. ONE code is
|
|
235
|
+
* client-originated rather than a pod wire code: `STREAM_STALLED` (see
|
|
236
|
+
* `CLIENT_ERROR_CODES`), set when the guuey#192 stall watchdog gives up.
|
|
237
|
+
*/
|
|
238
|
+
errorCode: string | null;
|
|
165
239
|
threadId: string | null;
|
|
166
240
|
/** Abort the in-flight turn (the stream stops; partial text is kept). */
|
|
167
241
|
abort: () => void;
|
|
@@ -220,5 +294,29 @@ export interface UseAgentInvokeReturn {
|
|
|
220
294
|
profileLinkRequest: ProfileLinkRequest | null;
|
|
221
295
|
/** Dismiss the pending {@link profileLinkRequest} (back to `null`). */
|
|
222
296
|
clearProfileLinkRequest: () => void;
|
|
297
|
+
/**
|
|
298
|
+
* The LAST turn ended by user abort (`abort()` or the external signal) —
|
|
299
|
+
* the transcript renderer's R1 aborted-partial + "Stopped." signal.
|
|
300
|
+
* Distinct from the #192 watchdog's internal stream abort, which never
|
|
301
|
+
* sets it. Cleared by the next `send()` and by `reset()`.
|
|
302
|
+
*/
|
|
303
|
+
aborted: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* The LAST turn was ADOPTED from persisted history by the guuey#192 stall
|
|
306
|
+
* watchdog (the reply was already durably written; the dead stream was
|
|
307
|
+
* discarded). Presentation contract: calm surfaces render an adopted turn
|
|
308
|
+
* identically to a streamed one; debug surfaces may mark it. Cleared by
|
|
309
|
+
* the next `send()` and by `reset()`.
|
|
310
|
+
*/
|
|
311
|
+
adopted: boolean;
|
|
312
|
+
/**
|
|
313
|
+
* The optimistic-send lifecycle, keyed by each live user turn's
|
|
314
|
+
* `clientMessageId` (see {@link AgentMessage.clientMessageId}):
|
|
315
|
+
* `"sending"` until the pod's `session` frame admits the turn, `"failed"`
|
|
316
|
+
* when the turn errored BEFORE admission (the message never reached the
|
|
317
|
+
* agent — the R0 failed-to-send state with its retry affordance). Entries
|
|
318
|
+
* for admitted turns are removed (absent = sent). `reset()` clears it.
|
|
319
|
+
*/
|
|
320
|
+
sendStates: Readonly<Record<string, "sending" | "failed">>;
|
|
223
321
|
}
|
|
224
322
|
//# sourceMappingURL=types.d.ts.map
|
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;AAEtE,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,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,4 @@
|
|
|
1
|
-
import type { AgentMessage, HistoryLoadResult, UseAgentInvokeOptions, UseAgentInvokeReturn } from "./types";
|
|
1
|
+
import type { AgentMessage, HistoryLoadResult, UseAgentInvokeOptions, UseAgentInvokeReturn } from "./types.js";
|
|
2
2
|
/** The decision `applyHistoryResult` reaches for a loaded transcript. */
|
|
3
3
|
export type HistoryApplication = {
|
|
4
4
|
kind: "seed";
|
|
@@ -16,5 +16,33 @@ export type HistoryApplication = {
|
|
|
16
16
|
* mid-flight send always beats late-arriving history.
|
|
17
17
|
*/
|
|
18
18
|
export declare function applyHistoryResult(result: HistoryLoadResult, currentMessages: AgentMessage[]): HistoryApplication;
|
|
19
|
+
/** The guuey#192 stall watchdog's resolved tuning (see {@link stallProbeDecision}). */
|
|
20
|
+
export declare const STALL_RECOVERY_DEFAULTS: {
|
|
21
|
+
readonly windowMs: 25000;
|
|
22
|
+
readonly probeAttempts: 4;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Pure decision seam for the guuey#192 stall probe: does a freshly-loaded
|
|
26
|
+
* transcript already contain THIS turn's finished reply?
|
|
27
|
+
*
|
|
28
|
+
* `adopt` requires BOTH signals, because each alone lies in a real case:
|
|
29
|
+
*
|
|
30
|
+
* - **user-count**: history must hold at least as many user turns as the
|
|
31
|
+
* local transcript (which includes the just-sent optimistic one). Without
|
|
32
|
+
* it, a thread whose PREVIOUS turn ended in a completed assistant reply
|
|
33
|
+
* would adopt that OLD transcript and silently drop the in-flight turn.
|
|
34
|
+
* - **finished tail**: history's last message must be a non-empty assistant
|
|
35
|
+
* reply. Without it, a history read that caught the persisted user row
|
|
36
|
+
* before the assistant row would adopt a reply-less transcript.
|
|
37
|
+
*
|
|
38
|
+
* KNOWN LIMIT (documented, accepted): the runtime persists a turn's rows at
|
|
39
|
+
* completion — the guuey#192 evidence (a reload mid-stall renders the FULL
|
|
40
|
+
* reply) is only possible under that model, and the read plane carries no
|
|
41
|
+
* per-row clientMessageId to match against. If persistence ever becomes
|
|
42
|
+
* progressive (partial assistant rows), this heuristic needs the read plane
|
|
43
|
+
* to grow a turn-completion marker — do not "fix" it client-side by text
|
|
44
|
+
* comparison, which cannot distinguish a partial row from a finished one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function stallProbeDecision(history: AgentMessage[], localUserCount: number): "adopt" | "in-flight";
|
|
19
47
|
export declare function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeReturn;
|
|
20
48
|
//# sourceMappingURL=useAgentInvoke.d.ts.map
|
|
@@ -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":"AA6BA,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAIjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB,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,CAmehF"}
|