@tangle-network/agent-app 0.43.45 → 0.43.47
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/chat-routes/index.d.ts +14 -157
- package/dist/chat-routes/index.js +6 -94
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/{chunk-B5JD3DXD.js → chunk-4AUQIAYU.js} +87 -2
- package/dist/chunk-4AUQIAYU.js.map +1 -0
- package/dist/chunk-BQV42AFK.js +99 -0
- package/dist/chunk-BQV42AFK.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +18 -2
- package/dist/stale-turn-lock-C8Na1cFZ.d.ts +149 -0
- package/dist/stream/index.d.ts +33 -146
- package/dist/stream/index.js +19 -3
- package/dist/teams/index.js +9 -9
- package/dist/teams/invitations-api.js +7 -7
- package/dist/turn-buffer-C9mEgoop.d.ts +146 -0
- package/dist/turn-stream/index.d.ts +551 -0
- package/dist/turn-stream/index.js +837 -0
- package/dist/turn-stream/index.js.map +1 -0
- package/package.json +6 -1
- package/dist/chunk-B5JD3DXD.js.map +0 -1
package/dist/stream/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { b as ChatInteractionStatus } from '../contract-KfqJh_au.js';
|
|
2
|
+
export { B as BufferedTurnEvent, a as BufferedTurnOptions, b as BufferedTurnTap, D as D1LikeForTurns, P as PumpBufferedTurnOptions, R as ReplayTurnEventsOptions, T as TURN_EVENTS_MIGRATION_SQL, c as TURN_STATUS_SCOPE_MIGRATION_SQL, d as TurnEventStore, e as TurnStatus, f as coalesceChatStreamEvents, g as coalesceDeltas, h as createBufferedTurnTap, i as createD1TurnEventStore, j as createMemoryTurnEventStore, p as pumpBufferedTurn, r as replayTurnEvents } from '../turn-buffer-C9mEgoop.js';
|
|
3
|
+
import '@tangle-network/agent-interface';
|
|
4
|
+
|
|
1
5
|
type JsonRecord = Record<string, unknown>;
|
|
2
6
|
interface StreamEvent {
|
|
3
7
|
type: string;
|
|
@@ -10,9 +14,37 @@ declare function resolveToolName(part: JsonRecord): string;
|
|
|
10
14
|
declare function normalizeTime(value: unknown): JsonRecord | undefined;
|
|
11
15
|
declare function normalizeToolEvent(event: StreamEvent): StreamEvent;
|
|
12
16
|
declare function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null;
|
|
17
|
+
/** Stream/transcript part key for a promoted (path-bearing) attachment,
|
|
18
|
+
* keyed on its storage path — re-emitting the same path folds into the same
|
|
19
|
+
* segment instead of duplicating it. */
|
|
20
|
+
declare function attachmentPartKey(path: string): string;
|
|
13
21
|
declare function getPartKey(part: JsonRecord): string;
|
|
14
22
|
declare function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord;
|
|
23
|
+
declare const MISSING_TOOL_TERMINAL_ERROR = "Tool did not report a terminal result before the assistant turn completed.";
|
|
24
|
+
declare const MISSING_TOOL_TERMINAL_REASON = "missing-tool-terminal";
|
|
25
|
+
/** Closes a tool part left `running` when a stream ended abnormally: settles
|
|
26
|
+
* it as a terminal `error` and stamps `state.metadata.terminalized` so the
|
|
27
|
+
* synthetic settlement is distinguishable from a real tool failure. Parts
|
|
28
|
+
* that already settled (and non-tool parts) pass through untouched. */
|
|
29
|
+
declare function terminalizeDanglingToolPart(part: JsonRecord): JsonRecord;
|
|
30
|
+
declare function terminalizeDanglingToolParts(parts: JsonRecord[]): JsonRecord[];
|
|
31
|
+
/** Settles still-pending interaction parts at persist time. The broker
|
|
32
|
+
* guarantees a resolved question either answered (run unblocked, no cancel
|
|
33
|
+
* event) or cancelled/timed out (cancel event already updated the part), so
|
|
34
|
+
* the success path finalizes remaining pendings as `answered` and the
|
|
35
|
+
* failure/terminalize paths as `expired`. */
|
|
36
|
+
declare function finalizePendingInteractionParts(parts: JsonRecord[], outcome: Extract<ChatInteractionStatus, 'answered' | 'expired'>): JsonRecord[];
|
|
37
|
+
/** Collapses text-part artifacts of unstable upstream segment identity: the
|
|
38
|
+
* same text arriving under two keys (id-less delta stream, then an
|
|
39
|
+
* id-bearing snapshot) folds into two segments, and interleaved empty
|
|
40
|
+
* segments survive as blank parts. Consecutive identical text parts merge
|
|
41
|
+
* into one; empty text parts drop when any non-empty text part exists. */
|
|
42
|
+
declare function collapseRedundantTextParts(parts: JsonRecord[]): JsonRecord[];
|
|
15
43
|
declare function finalizeAssistantParts(partOrder: string[], partMap: Map<string, JsonRecord>, finalText: string): JsonRecord[];
|
|
44
|
+
/** Finalizes, then folds each synthetic tool settlement back into `partMap`
|
|
45
|
+
* and returns just those updates — the shape a streaming loop needs to emit
|
|
46
|
+
* closing `message.part.updated` frames for tools the stream never settled. */
|
|
47
|
+
declare function terminalizeDanglingAssistantToolUpdates(partOrder: string[], partMap: Map<string, JsonRecord>, finalText: string): JsonRecord[];
|
|
16
48
|
declare function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array;
|
|
17
49
|
|
|
18
50
|
interface PersistedChatMessageForTurn {
|
|
@@ -36,149 +68,4 @@ declare function resolveChatTurn(input: {
|
|
|
36
68
|
turnId?: string;
|
|
37
69
|
}): ResolvedChatTurn;
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
* Resumable chat turns — the router-path answer to "streams resume on
|
|
41
|
-
* disconnect" (issue #27). A turn's loop events are teed into a store as they
|
|
42
|
-
* stream; the turn keeps running under `ctx.waitUntil` when the client drops;
|
|
43
|
-
* a reconnecting client replays the buffered tail by sequence number and
|
|
44
|
-
* keeps following until the turn completes.
|
|
45
|
-
*
|
|
46
|
-
* POST /chat/stream → pumpBufferedTurn(...) + live NDJSON
|
|
47
|
-
* GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON
|
|
48
|
-
*
|
|
49
|
-
* Storage is a structural seam ({@link TurnEventStore}); a D1 implementation
|
|
50
|
-
* ships here because that's what Cloudflare products have (KV is unsuitable:
|
|
51
|
-
* eventually consistent cross-isolate). Per-token deltas would mean hundreds
|
|
52
|
-
* of rows per turn, so consecutive text/reasoning deltas are coalesced within
|
|
53
|
-
* a flush window before they are persisted — replay yields slightly chunkier
|
|
54
|
-
* deltas with identical concatenation.
|
|
55
|
-
*/
|
|
56
|
-
type TurnStatus = 'running' | 'complete' | 'error';
|
|
57
|
-
interface BufferedTurnEvent {
|
|
58
|
-
seq: number;
|
|
59
|
-
/** The serialized event line (JSON string, no trailing newline). */
|
|
60
|
-
event: string;
|
|
61
|
-
}
|
|
62
|
-
interface TurnEventStore {
|
|
63
|
-
append(turnId: string, events: BufferedTurnEvent[]): Promise<void>;
|
|
64
|
-
read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>;
|
|
65
|
-
/** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets
|
|
66
|
-
* {@link TurnEventStore.listRunning} rediscover this turn after a client reload
|
|
67
|
-
* loses the turnId; stores that don't track scope ignore it. */
|
|
68
|
-
setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>;
|
|
69
|
-
getStatus(turnId: string): Promise<TurnStatus | null>;
|
|
70
|
-
/** Running turnIds for a scope, newest first — so a reloaded client (clientRunId
|
|
71
|
-
* lost) can find and resume the in-flight turn. Optional: a store records it
|
|
72
|
-
* only if `setStatus` was given a `scopeId`. */
|
|
73
|
-
listRunning?(scopeId: string): Promise<string[]>;
|
|
74
|
-
}
|
|
75
|
-
/** Merge consecutive text/reasoning deltas of the same type into one event.
|
|
76
|
-
* Concatenation-preserving: replaying the coalesced stream produces the same
|
|
77
|
-
* accumulated text as the original. */
|
|
78
|
-
declare function coalesceDeltas(events: unknown[]): unknown[];
|
|
79
|
-
/**
|
|
80
|
-
* Coalesce consecutive `message.part.updated` deltas for the SAME part into one
|
|
81
|
-
* event. agent-runtime products stream `ChatStreamEvent` NDJSON
|
|
82
|
-
* (`{type:'message.part.updated', data:{part, delta}}`); pumped through the
|
|
83
|
-
* buffer with the default tool-loop coalescer, every per-token delta persists as
|
|
84
|
-
* its own row because that coalescer never recognizes the shape. Pass this as
|
|
85
|
-
* {@link PumpBufferedTurnOptions.coalesce} instead.
|
|
86
|
-
*
|
|
87
|
-
* Concatenation-preserving for BOTH consumer styles: the merged event keeps the
|
|
88
|
-
* LATEST event's `data.part` (already the cumulative accumulation) and sets
|
|
89
|
-
* `data.delta` to the concatenation of the merged deltas, so a client that
|
|
90
|
-
* appends `delta` and one that reads the cumulative `part` both reconstruct the
|
|
91
|
-
* identical final text.
|
|
92
|
-
*/
|
|
93
|
-
declare function coalesceChatStreamEvents(events: unknown[]): unknown[];
|
|
94
|
-
interface BufferedTurnOptions {
|
|
95
|
-
store: TurnEventStore;
|
|
96
|
-
turnId: string;
|
|
97
|
-
/** Deliver one serialized line to the live client. Throwing here (client
|
|
98
|
-
* disconnected) does NOT stop buffering — events keep persisting. */
|
|
99
|
-
write?: (line: string) => Promise<void> | void;
|
|
100
|
-
/** Flush buffered events to the store at most this often. Default 400ms. */
|
|
101
|
-
flushIntervalMs?: number;
|
|
102
|
-
/** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning
|
|
103
|
-
* deltas). agent-runtime products streaming `ChatStreamEvent` pass
|
|
104
|
-
* {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a
|
|
105
|
-
* row. Must be concatenation-preserving. */
|
|
106
|
-
coalesce?: (events: unknown[]) => unknown[];
|
|
107
|
-
/** Optional scope (thread/session id) recorded with the turn status, so
|
|
108
|
-
* {@link TurnEventStore.listRunning} can find this turn after a reload. */
|
|
109
|
-
scopeId?: string;
|
|
110
|
-
}
|
|
111
|
-
/** A push-driven buffer for a turn whose producer the caller does NOT own. */
|
|
112
|
-
interface BufferedTurnTap {
|
|
113
|
-
/** Buffer one event: persist (coalesced, on the flush window) + best-effort
|
|
114
|
-
* live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime
|
|
115
|
-
* `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */
|
|
116
|
-
onEvent(raw: unknown): Promise<void>;
|
|
117
|
-
/** Settle the turn: final flush + set status. Call after the producer resolves
|
|
118
|
-
* ('complete') or rejects ('error'). 'error' flushes what was produced first. */
|
|
119
|
-
done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* The buffering core. Sequence-numbers every event, delivers it to `write`
|
|
123
|
-
* (best-effort — a disconnected client never stops buffering), and flushes to
|
|
124
|
-
* the store in coalesced batches. Drives both transports:
|
|
125
|
-
*
|
|
126
|
-
* • {@link pumpBufferedTurn} — when you OWN an `AsyncIterable` producer.
|
|
127
|
-
* • this tap (`onEvent`/`done`) — when the producer owns iteration and only
|
|
128
|
-
* hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`
|
|
129
|
-
* + the finished body). Durability stays here in the shell; the engine needs
|
|
130
|
-
* no `TurnEventStore` seam.
|
|
131
|
-
*/
|
|
132
|
-
declare function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap;
|
|
133
|
-
interface PumpBufferedTurnOptions extends BufferedTurnOptions {
|
|
134
|
-
source: AsyncIterable<unknown>;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Drive a turn to completion regardless of the live client, when you OWN the
|
|
138
|
-
* producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.
|
|
139
|
-
* Returns a promise that resolves when the turn finishes — hand it to
|
|
140
|
-
* `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on
|
|
141
|
-
* client-write failure; a source error marks the turn 'error' (after flushing
|
|
142
|
-
* what was produced) and rethrows.
|
|
143
|
-
*/
|
|
144
|
-
declare function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void>;
|
|
145
|
-
interface ReplayTurnEventsOptions {
|
|
146
|
-
store: TurnEventStore;
|
|
147
|
-
turnId: string;
|
|
148
|
-
/** Replay strictly after this sequence number (0 = from the beginning). */
|
|
149
|
-
fromSeq?: number;
|
|
150
|
-
/** Poll cadence while the turn is still running. Default 500ms. */
|
|
151
|
-
pollMs?: number;
|
|
152
|
-
/** Give up following a 'running' turn after this long. Default 120s. */
|
|
153
|
-
timeoutMs?: number;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Yield buffered events after `fromSeq`, then keep polling while the turn is
|
|
157
|
-
* still 'running' until it completes, errors, or times out. Terminates with a
|
|
158
|
-
* final `{seq: -1, event: '{"type":"turn_status",...}'}` marker so clients
|
|
159
|
-
* know why the replay ended.
|
|
160
|
-
*/
|
|
161
|
-
declare function replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent>;
|
|
162
|
-
/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */
|
|
163
|
-
interface D1LikeForTurns {
|
|
164
|
-
prepare(sql: string): {
|
|
165
|
-
bind(...values: unknown[]): {
|
|
166
|
-
run(): Promise<unknown>;
|
|
167
|
-
all<T = Record<string, unknown>>(): Promise<{
|
|
168
|
-
results: T[];
|
|
169
|
-
}>;
|
|
170
|
-
first<T = Record<string, unknown>>(): Promise<T | null>;
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
/** Schema for the D1 store — append to the product's migrations. */
|
|
175
|
-
declare const TURN_EVENTS_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n scopeId TEXT,\n updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n";
|
|
176
|
-
/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —
|
|
177
|
-
* run once to add the column (the CREATE above already includes it for new
|
|
178
|
-
* deployments). SQLite ignores a duplicate-add error if already applied. */
|
|
179
|
-
declare const TURN_STATUS_SCOPE_MIGRATION_SQL = "ALTER TABLE turn_status ADD COLUMN scopeId TEXT;";
|
|
180
|
-
declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
|
|
181
|
-
/** In-memory store for tests and keyless local dev. */
|
|
182
|
-
declare function createMemoryTurnEventStore(): TurnEventStore;
|
|
183
|
-
|
|
184
|
-
export { type BufferedTurnEvent, type BufferedTurnOptions, type BufferedTurnTap, type D1LikeForTurns, type JsonRecord, type PersistedChatMessageForTurn, type PumpBufferedTurnOptions, type ReplayTurnEventsOptions, type ResolvedChatTurn, type StreamEvent, TURN_EVENTS_MIGRATION_SQL, TURN_STATUS_SCOPE_MIGRATION_SQL, type TurnEventStore, type TurnStatus, asRecord, asString, buildUserTextParts, coalesceChatStreamEvents, coalesceDeltas, createBufferedTurnTap, createD1TurnEventStore, createMemoryTurnEventStore, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, pumpBufferedTurn, replayTurnEvents, resolveChatTurn, resolveToolId, resolveToolName };
|
|
71
|
+
export { type JsonRecord, MISSING_TOOL_TERMINAL_ERROR, MISSING_TOOL_TERMINAL_REASON, type PersistedChatMessageForTurn, type ResolvedChatTurn, type StreamEvent, asRecord, asString, attachmentPartKey, buildUserTextParts, collapseRedundantTextParts, encodeEvent, finalizeAssistantParts, finalizePendingInteractionParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, resolveChatTurn, resolveToolId, resolveToolName, terminalizeDanglingAssistantToolUpdates, terminalizeDanglingToolPart, terminalizeDanglingToolParts };
|
package/dist/stream/index.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MISSING_TOOL_TERMINAL_ERROR,
|
|
3
|
+
MISSING_TOOL_TERMINAL_REASON,
|
|
2
4
|
TURN_EVENTS_MIGRATION_SQL,
|
|
3
5
|
TURN_STATUS_SCOPE_MIGRATION_SQL,
|
|
4
6
|
asRecord,
|
|
5
7
|
asString,
|
|
8
|
+
attachmentPartKey,
|
|
6
9
|
buildUserTextParts,
|
|
7
10
|
coalesceChatStreamEvents,
|
|
8
11
|
coalesceDeltas,
|
|
12
|
+
collapseRedundantTextParts,
|
|
9
13
|
createBufferedTurnTap,
|
|
10
14
|
createD1TurnEventStore,
|
|
11
15
|
createMemoryTurnEventStore,
|
|
12
16
|
encodeEvent,
|
|
13
17
|
finalizeAssistantParts,
|
|
18
|
+
finalizePendingInteractionParts,
|
|
14
19
|
getPartKey,
|
|
15
20
|
mergePersistedPart,
|
|
16
21
|
messageHasTurnId,
|
|
@@ -22,23 +27,31 @@ import {
|
|
|
22
27
|
replayTurnEvents,
|
|
23
28
|
resolveChatTurn,
|
|
24
29
|
resolveToolId,
|
|
25
|
-
resolveToolName
|
|
26
|
-
|
|
30
|
+
resolveToolName,
|
|
31
|
+
terminalizeDanglingAssistantToolUpdates,
|
|
32
|
+
terminalizeDanglingToolPart,
|
|
33
|
+
terminalizeDanglingToolParts
|
|
34
|
+
} from "../chunk-4AUQIAYU.js";
|
|
27
35
|
import "../chunk-XAWFPMAR.js";
|
|
28
36
|
import "../chunk-SIXYZ2FB.js";
|
|
29
37
|
export {
|
|
38
|
+
MISSING_TOOL_TERMINAL_ERROR,
|
|
39
|
+
MISSING_TOOL_TERMINAL_REASON,
|
|
30
40
|
TURN_EVENTS_MIGRATION_SQL,
|
|
31
41
|
TURN_STATUS_SCOPE_MIGRATION_SQL,
|
|
32
42
|
asRecord,
|
|
33
43
|
asString,
|
|
44
|
+
attachmentPartKey,
|
|
34
45
|
buildUserTextParts,
|
|
35
46
|
coalesceChatStreamEvents,
|
|
36
47
|
coalesceDeltas,
|
|
48
|
+
collapseRedundantTextParts,
|
|
37
49
|
createBufferedTurnTap,
|
|
38
50
|
createD1TurnEventStore,
|
|
39
51
|
createMemoryTurnEventStore,
|
|
40
52
|
encodeEvent,
|
|
41
53
|
finalizeAssistantParts,
|
|
54
|
+
finalizePendingInteractionParts,
|
|
42
55
|
getPartKey,
|
|
43
56
|
mergePersistedPart,
|
|
44
57
|
messageHasTurnId,
|
|
@@ -50,6 +63,9 @@ export {
|
|
|
50
63
|
replayTurnEvents,
|
|
51
64
|
resolveChatTurn,
|
|
52
65
|
resolveToolId,
|
|
53
|
-
resolveToolName
|
|
66
|
+
resolveToolName,
|
|
67
|
+
terminalizeDanglingAssistantToolUpdates,
|
|
68
|
+
terminalizeDanglingToolPart,
|
|
69
|
+
terminalizeDanglingToolParts
|
|
54
70
|
};
|
|
55
71
|
//# sourceMappingURL=index.js.map
|
package/dist/teams/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INVITATION_EXPIRY_DAYS,
|
|
3
|
+
generateInvitationToken,
|
|
4
|
+
getInvitationExpiresAt,
|
|
5
|
+
inviteUrlForToken,
|
|
6
|
+
normalizeInvitationEmail,
|
|
7
|
+
parseInvitationPermission,
|
|
8
|
+
renderInvitationEmail
|
|
9
|
+
} from "../chunk-WEBBJBDH.js";
|
|
1
10
|
import {
|
|
2
11
|
generateInviteToken,
|
|
3
12
|
isInviteTokenShape,
|
|
@@ -18,15 +27,6 @@ import {
|
|
|
18
27
|
workspaceRoleToCollaborationAccess,
|
|
19
28
|
workspaceRoleToSandboxRole
|
|
20
29
|
} from "../chunk-63CE7FEZ.js";
|
|
21
|
-
import {
|
|
22
|
-
INVITATION_EXPIRY_DAYS,
|
|
23
|
-
generateInvitationToken,
|
|
24
|
-
getInvitationExpiresAt,
|
|
25
|
-
inviteUrlForToken,
|
|
26
|
-
normalizeInvitationEmail,
|
|
27
|
-
parseInvitationPermission,
|
|
28
|
-
renderInvitationEmail
|
|
29
|
-
} from "../chunk-WEBBJBDH.js";
|
|
30
30
|
export {
|
|
31
31
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
32
32
|
INVITATION_EXPIRY_DAYS,
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SeatLimitError
|
|
3
|
-
} from "../chunk-SWUVTGMR.js";
|
|
4
|
-
import "../chunk-3SVAA3MA.js";
|
|
5
|
-
import {
|
|
6
|
-
hasWorkspaceRole
|
|
7
|
-
} from "../chunk-63CE7FEZ.js";
|
|
8
1
|
import {
|
|
9
2
|
generateInvitationToken,
|
|
10
3
|
getInvitationExpiresAt,
|
|
@@ -12,6 +5,13 @@ import {
|
|
|
12
5
|
normalizeInvitationEmail,
|
|
13
6
|
parseInvitationPermission
|
|
14
7
|
} from "../chunk-WEBBJBDH.js";
|
|
8
|
+
import {
|
|
9
|
+
SeatLimitError
|
|
10
|
+
} from "../chunk-SWUVTGMR.js";
|
|
11
|
+
import "../chunk-3SVAA3MA.js";
|
|
12
|
+
import {
|
|
13
|
+
hasWorkspaceRole
|
|
14
|
+
} from "../chunk-63CE7FEZ.js";
|
|
15
15
|
|
|
16
16
|
// src/teams/invitations-api.ts
|
|
17
17
|
import { and, eq, lte, sql } from "drizzle-orm";
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resumable chat turns — the router-path answer to "streams resume on
|
|
3
|
+
* disconnect" (issue #27). A turn's loop events are teed into a store as they
|
|
4
|
+
* stream; the turn keeps running under `ctx.waitUntil` when the client drops;
|
|
5
|
+
* a reconnecting client replays the buffered tail by sequence number and
|
|
6
|
+
* keeps following until the turn completes.
|
|
7
|
+
*
|
|
8
|
+
* POST /chat/stream → pumpBufferedTurn(...) + live NDJSON
|
|
9
|
+
* GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON
|
|
10
|
+
*
|
|
11
|
+
* Storage is a structural seam ({@link TurnEventStore}); a D1 implementation
|
|
12
|
+
* ships here because that's what Cloudflare products have (KV is unsuitable:
|
|
13
|
+
* eventually consistent cross-isolate). Per-token deltas would mean hundreds
|
|
14
|
+
* of rows per turn, so consecutive text/reasoning deltas are coalesced within
|
|
15
|
+
* a flush window before they are persisted — replay yields slightly chunkier
|
|
16
|
+
* deltas with identical concatenation.
|
|
17
|
+
*/
|
|
18
|
+
type TurnStatus = 'running' | 'complete' | 'error';
|
|
19
|
+
interface BufferedTurnEvent {
|
|
20
|
+
seq: number;
|
|
21
|
+
/** The serialized event line (JSON string, no trailing newline). */
|
|
22
|
+
event: string;
|
|
23
|
+
}
|
|
24
|
+
interface TurnEventStore {
|
|
25
|
+
append(turnId: string, events: BufferedTurnEvent[]): Promise<void>;
|
|
26
|
+
read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>;
|
|
27
|
+
/** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets
|
|
28
|
+
* {@link TurnEventStore.listRunning} rediscover this turn after a client reload
|
|
29
|
+
* loses the turnId; stores that don't track scope ignore it. */
|
|
30
|
+
setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>;
|
|
31
|
+
getStatus(turnId: string): Promise<TurnStatus | null>;
|
|
32
|
+
/** Running turnIds for a scope, newest first — so a reloaded client (clientRunId
|
|
33
|
+
* lost) can find and resume the in-flight turn. Optional: a store records it
|
|
34
|
+
* only if `setStatus` was given a `scopeId`. */
|
|
35
|
+
listRunning?(scopeId: string): Promise<string[]>;
|
|
36
|
+
}
|
|
37
|
+
/** Merge consecutive text/reasoning deltas of the same type into one event.
|
|
38
|
+
* Concatenation-preserving: replaying the coalesced stream produces the same
|
|
39
|
+
* accumulated text as the original. */
|
|
40
|
+
declare function coalesceDeltas(events: unknown[]): unknown[];
|
|
41
|
+
/**
|
|
42
|
+
* Coalesce consecutive `message.part.updated` deltas for the SAME part into one
|
|
43
|
+
* event. agent-runtime products stream `ChatStreamEvent` NDJSON
|
|
44
|
+
* (`{type:'message.part.updated', data:{part, delta}}`); pumped through the
|
|
45
|
+
* buffer with the default tool-loop coalescer, every per-token delta persists as
|
|
46
|
+
* its own row because that coalescer never recognizes the shape. Pass this as
|
|
47
|
+
* {@link PumpBufferedTurnOptions.coalesce} instead.
|
|
48
|
+
*
|
|
49
|
+
* Concatenation-preserving for BOTH consumer styles: the merged event keeps the
|
|
50
|
+
* LATEST event's `data.part` (already the cumulative accumulation) and sets
|
|
51
|
+
* `data.delta` to the concatenation of the merged deltas, so a client that
|
|
52
|
+
* appends `delta` and one that reads the cumulative `part` both reconstruct the
|
|
53
|
+
* identical final text.
|
|
54
|
+
*/
|
|
55
|
+
declare function coalesceChatStreamEvents(events: unknown[]): unknown[];
|
|
56
|
+
interface BufferedTurnOptions {
|
|
57
|
+
store: TurnEventStore;
|
|
58
|
+
turnId: string;
|
|
59
|
+
/** Deliver one serialized line to the live client. Throwing here (client
|
|
60
|
+
* disconnected) does NOT stop buffering — events keep persisting. */
|
|
61
|
+
write?: (line: string) => Promise<void> | void;
|
|
62
|
+
/** Flush buffered events to the store at most this often. Default 400ms. */
|
|
63
|
+
flushIntervalMs?: number;
|
|
64
|
+
/** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning
|
|
65
|
+
* deltas). agent-runtime products streaming `ChatStreamEvent` pass
|
|
66
|
+
* {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a
|
|
67
|
+
* row. Must be concatenation-preserving. */
|
|
68
|
+
coalesce?: (events: unknown[]) => unknown[];
|
|
69
|
+
/** Optional scope (thread/session id) recorded with the turn status, so
|
|
70
|
+
* {@link TurnEventStore.listRunning} can find this turn after a reload. */
|
|
71
|
+
scopeId?: string;
|
|
72
|
+
}
|
|
73
|
+
/** A push-driven buffer for a turn whose producer the caller does NOT own. */
|
|
74
|
+
interface BufferedTurnTap {
|
|
75
|
+
/** Buffer one event: persist (coalesced, on the flush window) + best-effort
|
|
76
|
+
* live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime
|
|
77
|
+
* `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */
|
|
78
|
+
onEvent(raw: unknown): Promise<void>;
|
|
79
|
+
/** Settle the turn: final flush + set status. Call after the producer resolves
|
|
80
|
+
* ('complete') or rejects ('error'). 'error' flushes what was produced first. */
|
|
81
|
+
done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The buffering core. Sequence-numbers every event, delivers it to `write`
|
|
85
|
+
* (best-effort — a disconnected client never stops buffering), and flushes to
|
|
86
|
+
* the store in coalesced batches. Drives both transports:
|
|
87
|
+
*
|
|
88
|
+
* • {@link pumpBufferedTurn} — when you OWN an `AsyncIterable` producer.
|
|
89
|
+
* • this tap (`onEvent`/`done`) — when the producer owns iteration and only
|
|
90
|
+
* hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`
|
|
91
|
+
* + the finished body). Durability stays here in the shell; the engine needs
|
|
92
|
+
* no `TurnEventStore` seam.
|
|
93
|
+
*/
|
|
94
|
+
declare function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap;
|
|
95
|
+
interface PumpBufferedTurnOptions extends BufferedTurnOptions {
|
|
96
|
+
source: AsyncIterable<unknown>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Drive a turn to completion regardless of the live client, when you OWN the
|
|
100
|
+
* producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.
|
|
101
|
+
* Returns a promise that resolves when the turn finishes — hand it to
|
|
102
|
+
* `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on
|
|
103
|
+
* client-write failure; a source error marks the turn 'error' (after flushing
|
|
104
|
+
* what was produced) and rethrows.
|
|
105
|
+
*/
|
|
106
|
+
declare function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void>;
|
|
107
|
+
interface ReplayTurnEventsOptions {
|
|
108
|
+
store: TurnEventStore;
|
|
109
|
+
turnId: string;
|
|
110
|
+
/** Replay strictly after this sequence number (0 = from the beginning). */
|
|
111
|
+
fromSeq?: number;
|
|
112
|
+
/** Poll cadence while the turn is still running. Default 500ms. */
|
|
113
|
+
pollMs?: number;
|
|
114
|
+
/** Give up following a 'running' turn after this long. Default 120s. */
|
|
115
|
+
timeoutMs?: number;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Yield buffered events after `fromSeq`, then keep polling while the turn is
|
|
119
|
+
* still 'running' until it completes, errors, or times out. Terminates with a
|
|
120
|
+
* final `{seq: -1, event: '{"type":"turn_status",...}'}` marker so clients
|
|
121
|
+
* know why the replay ended.
|
|
122
|
+
*/
|
|
123
|
+
declare function replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent>;
|
|
124
|
+
/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */
|
|
125
|
+
interface D1LikeForTurns {
|
|
126
|
+
prepare(sql: string): {
|
|
127
|
+
bind(...values: unknown[]): {
|
|
128
|
+
run(): Promise<unknown>;
|
|
129
|
+
all<T = Record<string, unknown>>(): Promise<{
|
|
130
|
+
results: T[];
|
|
131
|
+
}>;
|
|
132
|
+
first<T = Record<string, unknown>>(): Promise<T | null>;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Schema for the D1 store — append to the product's migrations. */
|
|
137
|
+
declare const TURN_EVENTS_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n scopeId TEXT,\n updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n";
|
|
138
|
+
/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —
|
|
139
|
+
* run once to add the column (the CREATE above already includes it for new
|
|
140
|
+
* deployments). SQLite ignores a duplicate-add error if already applied. */
|
|
141
|
+
declare const TURN_STATUS_SCOPE_MIGRATION_SQL = "ALTER TABLE turn_status ADD COLUMN scopeId TEXT;";
|
|
142
|
+
declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
|
|
143
|
+
/** In-memory store for tests and keyless local dev. */
|
|
144
|
+
declare function createMemoryTurnEventStore(): TurnEventStore;
|
|
145
|
+
|
|
146
|
+
export { type BufferedTurnEvent as B, type D1LikeForTurns as D, type PumpBufferedTurnOptions as P, type ReplayTurnEventsOptions as R, TURN_EVENTS_MIGRATION_SQL as T, type BufferedTurnOptions as a, type BufferedTurnTap as b, TURN_STATUS_SCOPE_MIGRATION_SQL as c, type TurnEventStore as d, type TurnStatus as e, coalesceChatStreamEvents as f, coalesceDeltas as g, createBufferedTurnTap as h, createD1TurnEventStore as i, createMemoryTurnEventStore as j, pumpBufferedTurn as p, replayTurnEvents as r };
|