@amalgm/chat 0.1.1 → 0.2.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/AGENTS.md +98 -0
- package/PURPOSE.md +143 -104
- package/README.md +82 -54
- package/dist/acp/index.d.ts +7 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +7 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/projection.d.ts +30 -0
- package/dist/acp/projection.d.ts.map +1 -0
- package/dist/acp/projection.js +147 -0
- package/dist/acp/projection.js.map +1 -0
- package/dist/api/conversations.d.ts +66 -0
- package/dist/api/conversations.d.ts.map +1 -0
- package/dist/api/conversations.js +202 -0
- package/dist/api/conversations.js.map +1 -0
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -2
- package/dist/api/index.js.map +1 -1
- package/dist/conversations/index.d.ts +3 -0
- package/dist/conversations/index.d.ts.map +1 -0
- package/dist/conversations/index.js +3 -0
- package/dist/conversations/index.js.map +1 -0
- package/dist/conversations/transcript.d.ts +13 -0
- package/dist/conversations/transcript.d.ts.map +1 -0
- package/dist/conversations/transcript.js +171 -0
- package/dist/conversations/transcript.js.map +1 -0
- package/dist/conversations/types.d.ts +147 -0
- package/dist/conversations/types.d.ts.map +1 -0
- package/dist/conversations/types.js +9 -0
- package/dist/conversations/types.js.map +1 -0
- package/dist/events/constructors.d.ts +9 -15
- package/dist/events/constructors.d.ts.map +1 -1
- package/dist/events/constructors.js +6 -12
- package/dist/events/constructors.js.map +1 -1
- package/dist/events/index.d.ts +2 -3
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -3
- package/dist/events/index.js.map +1 -1
- package/dist/events/schema.d.ts +2 -5
- package/dist/events/schema.d.ts.map +1 -1
- package/dist/events/schema.js +2 -5
- package/dist/events/schema.js.map +1 -1
- package/dist/execution/contract.d.ts +51 -0
- package/dist/execution/contract.d.ts.map +1 -0
- package/dist/execution/contract.js +127 -0
- package/dist/execution/contract.js.map +1 -0
- package/dist/execution/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +4 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/revision.d.ts +5 -0
- package/dist/execution/revision.d.ts.map +1 -0
- package/dist/execution/revision.js +17 -0
- package/dist/execution/revision.js.map +1 -0
- package/dist/execution/store.d.ts +26 -0
- package/dist/execution/store.d.ts.map +1 -0
- package/dist/execution/store.js +38 -0
- package/dist/execution/store.js.map +1 -0
- package/dist/index.d.ts +8 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -23
- package/dist/index.js.map +1 -1
- package/dist/input/index.d.ts +3 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +2 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/prompt.d.ts +17 -0
- package/dist/input/prompt.d.ts.map +1 -0
- package/dist/input/prompt.js +104 -0
- package/dist/input/prompt.js.map +1 -0
- package/dist/normalizers/claude.d.ts +6 -9
- package/dist/normalizers/claude.d.ts.map +1 -1
- package/dist/normalizers/claude.js +5 -8
- package/dist/normalizers/claude.js.map +1 -1
- package/dist/normalizers/codex.d.ts +5 -8
- package/dist/normalizers/codex.d.ts.map +1 -1
- package/dist/normalizers/codex.js +4 -7
- package/dist/normalizers/codex.js.map +1 -1
- package/dist/normalizers/cursor.d.ts +1 -2
- package/dist/normalizers/cursor.d.ts.map +1 -1
- package/dist/normalizers/cursor.js +2 -3
- package/dist/normalizers/cursor.js.map +1 -1
- package/dist/normalizers/index.d.ts +3 -5
- package/dist/normalizers/index.d.ts.map +1 -1
- package/dist/normalizers/index.js +3 -5
- package/dist/normalizers/index.js.map +1 -1
- package/dist/normalizers/opencode.d.ts +2 -4
- package/dist/normalizers/opencode.d.ts.map +1 -1
- package/dist/normalizers/opencode.js +2 -4
- package/dist/normalizers/opencode.js.map +1 -1
- package/dist/normalizers/pi.d.ts +3 -4
- package/dist/normalizers/pi.d.ts.map +1 -1
- package/dist/normalizers/pi.js +3 -4
- package/dist/normalizers/pi.js.map +1 -1
- package/dist/sessions/chat.d.ts +176 -0
- package/dist/sessions/chat.d.ts.map +1 -0
- package/dist/sessions/chat.js +701 -0
- package/dist/sessions/chat.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/transcript/index.d.ts +1 -1
- package/dist/transcript/index.js +1 -1
- package/dist/transcript/message-reducer.d.ts +1 -1
- package/dist/transcript/message-reducer.js +1 -1
- package/dist/transcript/part-accumulator.d.ts +1 -1
- package/dist/transcript/part-accumulator.js +1 -1
- package/dist/transcript/tool-display.d.ts +1 -1
- package/dist/transcript/tool-display.js +1 -1
- package/dist/transcript/tool-shape.d.ts +1 -1
- package/dist/transcript/tool-shape.js +1 -1
- package/dist/usage/finalize.d.ts +7 -9
- package/dist/usage/finalize.d.ts.map +1 -1
- package/dist/usage/finalize.js +11 -13
- package/dist/usage/finalize.js.map +1 -1
- package/dist/usage/index.d.ts +2 -3
- package/dist/usage/index.d.ts.map +1 -1
- package/dist/usage/index.js +2 -3
- package/dist/usage/index.js.map +1 -1
- package/dist/usage/model-catalog.d.ts +2 -3
- package/dist/usage/model-catalog.d.ts.map +1 -1
- package/dist/usage/model-catalog.js +10 -3
- package/dist/usage/model-catalog.js.map +1 -1
- package/dist/usage/normalize.d.ts +5 -7
- package/dist/usage/normalize.d.ts.map +1 -1
- package/dist/usage/normalize.js +3 -5
- package/dist/usage/normalize.js.map +1 -1
- package/dist/usage/ports.d.ts +14 -24
- package/dist/usage/ports.d.ts.map +1 -1
- package/dist/usage/ports.js +9 -17
- package/dist/usage/ports.js.map +1 -1
- package/dist/usage/steps.d.ts +70 -0
- package/dist/usage/steps.d.ts.map +1 -0
- package/dist/usage/steps.js +152 -0
- package/dist/usage/steps.js.map +1 -0
- package/dist/usage/tokens.d.ts +4 -7
- package/dist/usage/tokens.d.ts.map +1 -1
- package/dist/usage/tokens.js +4 -7
- package/dist/usage/tokens.js.map +1 -1
- package/docs/contracts/acp-and-step-usage.md +51 -0
- package/docs/contracts/conversation-persistence.md +47 -0
- package/docs/contracts/input-and-execution.md +177 -0
- package/docs/contracts/platform-authorization.md +17 -0
- package/host/adapters/acp-capabilities.js +30 -0
- package/host/adapters/acp-client.js +156 -0
- package/host/adapters/acp.js +284 -0
- package/host/adapters/claude.js +235 -0
- package/host/adapters/codex.js +733 -0
- package/host/adapters/cursor.js +347 -0
- package/host/adapters/input-capabilities.js +89 -0
- package/host/adapters/opencode.js +486 -0
- package/host/adapters/pi.js +519 -0
- package/host/adapters/prompt.js +212 -0
- package/host/auth.js +257 -0
- package/host/credentials/store.js +209 -0
- package/host/http.d.ts +32 -0
- package/host/http.js +238 -0
- package/host/index.d.ts +84 -0
- package/host/index.js +59 -0
- package/host/lib/runtime-paths.js +44 -0
- package/host/model-resolution.js +313 -0
- package/host/native-contract.js +185 -0
- package/host/native-runtime.js +194 -0
- package/host/permissions.js +27 -0
- package/host/platform-egress.js +114 -0
- package/host/recorder.js +61 -0
- package/host/sqlite/index.d.ts +69 -0
- package/host/sqlite/index.js +704 -0
- package/host/tooling/mcp-bundle.js +214 -0
- package/host/tooling/mcp-relay.js +83 -0
- package/host/tooling/native-binaries.js +667 -0
- package/host/tooling/native-config.js +460 -0
- package/host/tooling/package-import.js +111 -0
- package/host/tooling/runtime-home.js +26 -0
- package/host/tooling/system-instructions.js +69 -0
- package/host/tooling/system-prompt.js +35 -0
- package/package.json +45 -21
- package/skills/chat/SKILL.md +97 -0
- package/skills/chat/agents/openai.yaml +4 -0
- package/skills/chat/references/contracts.md +131 -0
- package/dist/api/chat-payloads.d.ts +0 -10
- package/dist/api/chat-payloads.d.ts.map +0 -1
- package/dist/api/chat-payloads.js +0 -57
- package/dist/api/chat-payloads.js.map +0 -1
- package/dist/contract/chat-input.d.ts +0 -144
- package/dist/contract/chat-input.d.ts.map +0 -1
- package/dist/contract/chat-input.js +0 -261
- package/dist/contract/chat-input.js.map +0 -1
- package/dist/contract/create.d.ts +0 -191
- package/dist/contract/create.d.ts.map +0 -1
- package/dist/contract/create.js +0 -249
- package/dist/contract/create.js.map +0 -1
- package/dist/contract/freeze.d.ts +0 -85
- package/dist/contract/freeze.d.ts.map +0 -1
- package/dist/contract/freeze.js +0 -151
- package/dist/contract/freeze.js.map +0 -1
- package/dist/contract/harnesses.d.ts +0 -21
- package/dist/contract/harnesses.d.ts.map +0 -1
- package/dist/contract/harnesses.js +0 -39
- package/dist/contract/harnesses.js.map +0 -1
- package/dist/contract/index.d.ts +0 -17
- package/dist/contract/index.d.ts.map +0 -1
- package/dist/contract/index.js +0 -17
- package/dist/contract/index.js.map +0 -1
- package/dist/contract/input.d.ts +0 -45
- package/dist/contract/input.d.ts.map +0 -1
- package/dist/contract/input.js +0 -78
- package/dist/contract/input.js.map +0 -1
- package/dist/contract/models.d.ts +0 -57
- package/dist/contract/models.d.ts.map +0 -1
- package/dist/contract/models.js +0 -380
- package/dist/contract/models.js.map +0 -1
- package/dist/contract/payload-merge.d.ts +0 -76
- package/dist/contract/payload-merge.d.ts.map +0 -1
- package/dist/contract/payload-merge.js +0 -215
- package/dist/contract/payload-merge.js.map +0 -1
- package/dist/events/tool-display.d.ts +0 -22
- package/dist/events/tool-display.d.ts.map +0 -1
- package/dist/events/tool-display.js +0 -60
- package/dist/events/tool-display.js.map +0 -1
- package/dist/events/tool-shape.d.ts +0 -48
- package/dist/events/tool-shape.d.ts.map +0 -1
- package/dist/events/tool-shape.js +0 -225
- package/dist/events/tool-shape.js.map +0 -1
- package/dist/turns/index.d.ts +0 -11
- package/dist/turns/index.d.ts.map +0 -1
- package/dist/turns/index.js +0 -11
- package/dist/turns/index.js.map +0 -1
- package/dist/turns/queries.d.ts +0 -36
- package/dist/turns/queries.d.ts.map +0 -1
- package/dist/turns/queries.js +0 -37
- package/dist/turns/queries.js.map +0 -1
- package/dist/turns/replay.d.ts +0 -105
- package/dist/turns/replay.d.ts.map +0 -1
- package/dist/turns/replay.js +0 -163
- package/dist/turns/replay.js.map +0 -1
- package/dist/turns/status.d.ts +0 -49
- package/dist/turns/status.d.ts.map +0 -1
- package/dist/turns/status.js +0 -56
- package/dist/turns/status.js.map +0 -1
- package/dist/turns/turn-store.d.ts +0 -109
- package/dist/turns/turn-store.d.ts.map +0 -1
- package/dist/turns/turn-store.js +0 -169
- package/dist/turns/turn-store.js.map +0 -1
- package/dist/warm/client-cache.d.ts +0 -47
- package/dist/warm/client-cache.d.ts.map +0 -1
- package/dist/warm/client-cache.js +0 -48
- package/dist/warm/client-cache.js.map +0 -1
- package/dist/warm/envelope-cache.d.ts +0 -79
- package/dist/warm/envelope-cache.d.ts.map +0 -1
- package/dist/warm/envelope-cache.js +0 -85
- package/dist/warm/envelope-cache.js.map +0 -1
- package/dist/warm/index.d.ts +0 -18
- package/dist/warm/index.d.ts.map +0 -1
- package/dist/warm/index.js +0 -12
- package/dist/warm/index.js.map +0 -1
- package/dist/warm/payload.d.ts +0 -137
- package/dist/warm/payload.d.ts.map +0 -1
- package/dist/warm/payload.js +0 -112
- package/dist/warm/payload.js.map +0 -1
- package/dist/warm/revision.d.ts +0 -70
- package/dist/warm/revision.d.ts.map +0 -1
- package/dist/warm/revision.js +0 -63
- package/dist/warm/revision.js.map +0 -1
- package/dist/warm/store-policy.d.ts +0 -57
- package/dist/warm/store-policy.d.ts.map +0 -1
- package/dist/warm/store-policy.js +0 -89
- package/dist/warm/store-policy.js.map +0 -1
- package/dist/warm/store.d.ts +0 -42
- package/dist/warm/store.d.ts.map +0 -1
- package/dist/warm/store.js +0 -69
- package/dist/warm/store.js.map +0 -1
- package/dist/wire/frames.d.ts +0 -208
- package/dist/wire/frames.d.ts.map +0 -1
- package/dist/wire/frames.js +0 -245
- package/dist/wire/frames.js.map +0 -1
- package/dist/wire/index.d.ts +0 -7
- package/dist/wire/index.d.ts.map +0 -1
- package/dist/wire/index.js +0 -7
- package/dist/wire/index.js.map +0 -1
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
import { applyAcpTranscriptUpdate, createAcpTranscriptState, finalizeAcpTranscript, } from '../conversations/transcript.js';
|
|
2
|
+
import { normalizePrompt } from '../input/prompt.js';
|
|
3
|
+
import { normalizeExecutionContract } from '../execution/contract.js';
|
|
4
|
+
import { executionRevision } from '../execution/revision.js';
|
|
5
|
+
import { PreparedExecutionStore } from '../execution/store.js';
|
|
6
|
+
export class SessionNotFoundError extends Error {
|
|
7
|
+
code = 'SESSION_NOT_FOUND';
|
|
8
|
+
constructor(sessionId) {
|
|
9
|
+
super(`Session not found: ${sessionId}`);
|
|
10
|
+
this.name = 'SessionNotFoundError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class SessionAlreadyExistsError extends Error {
|
|
14
|
+
code = 'SESSION_ALREADY_EXISTS';
|
|
15
|
+
constructor(sessionId) {
|
|
16
|
+
super(`Session already exists: ${sessionId}`);
|
|
17
|
+
this.name = 'SessionAlreadyExistsError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class SessionBusyError extends Error {
|
|
21
|
+
code = 'SESSION_BUSY';
|
|
22
|
+
constructor(sessionId) {
|
|
23
|
+
super(`Session already has an active turn: ${sessionId}`);
|
|
24
|
+
this.name = 'SessionBusyError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class SessionAgentMismatchError extends Error {
|
|
28
|
+
code = 'SESSION_AGENT_MISMATCH';
|
|
29
|
+
constructor(sessionId) {
|
|
30
|
+
super(`Session belongs to a different agent installation: ${sessionId}`);
|
|
31
|
+
this.name = 'SessionAgentMismatchError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class TurnStreamError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
turn;
|
|
37
|
+
constructor(turn) {
|
|
38
|
+
const stored = turn.error;
|
|
39
|
+
super(typeof stored?.['message'] === 'string' ? stored['message'] : `Turn ${turn.status}: ${turn.id}`);
|
|
40
|
+
this.name = 'TurnStreamError';
|
|
41
|
+
this.code = typeof stored?.['code'] === 'string' ? stored['code'] : `TURN_${turn.status.toUpperCase()}`;
|
|
42
|
+
this.turn = turn;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class SubscriberBackpressureError extends Error {
|
|
46
|
+
code = 'SUBSCRIBER_BACKPRESSURE';
|
|
47
|
+
constructor(turnId) {
|
|
48
|
+
super(`Subscriber fell behind the durable turn journal: ${turnId}`);
|
|
49
|
+
this.name = 'SubscriberBackpressureError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class InterruptUnsupportedError extends Error {
|
|
53
|
+
code = 'INTERRUPT_UNSUPPORTED';
|
|
54
|
+
constructor(sessionId) {
|
|
55
|
+
super(`Runtime cannot interrupt the active turn: ${sessionId}`);
|
|
56
|
+
this.name = 'InterruptUnsupportedError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class LiveTurnBuffer {
|
|
60
|
+
#turnId;
|
|
61
|
+
#limit;
|
|
62
|
+
#updates = new Map();
|
|
63
|
+
#terminal = null;
|
|
64
|
+
#failure = null;
|
|
65
|
+
#wake = null;
|
|
66
|
+
constructor(turnId, limit) {
|
|
67
|
+
this.#turnId = turnId;
|
|
68
|
+
this.#limit = limit;
|
|
69
|
+
}
|
|
70
|
+
offer(update, afterSequence) {
|
|
71
|
+
if (update.sequence <= afterSequence || this.#failure)
|
|
72
|
+
return;
|
|
73
|
+
this.#updates.set(update.sequence, update);
|
|
74
|
+
if (this.#updates.size > this.#limit) {
|
|
75
|
+
this.#failure = new SubscriberBackpressureError(this.#turnId);
|
|
76
|
+
}
|
|
77
|
+
this.#notify();
|
|
78
|
+
}
|
|
79
|
+
finish(turn) {
|
|
80
|
+
this.#terminal = turn;
|
|
81
|
+
this.#notify();
|
|
82
|
+
}
|
|
83
|
+
fail(error) {
|
|
84
|
+
this.#failure = error;
|
|
85
|
+
this.#notify();
|
|
86
|
+
}
|
|
87
|
+
takeAfter(afterSequence) {
|
|
88
|
+
let selected = null;
|
|
89
|
+
for (const update of this.#updates.values()) {
|
|
90
|
+
if (update.sequence <= afterSequence) {
|
|
91
|
+
this.#updates.delete(update.sequence);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!selected || update.sequence < selected.sequence)
|
|
95
|
+
selected = update;
|
|
96
|
+
}
|
|
97
|
+
if (selected)
|
|
98
|
+
this.#updates.delete(selected.sequence);
|
|
99
|
+
return selected;
|
|
100
|
+
}
|
|
101
|
+
get terminal() {
|
|
102
|
+
return this.#terminal;
|
|
103
|
+
}
|
|
104
|
+
get failure() {
|
|
105
|
+
return this.#failure;
|
|
106
|
+
}
|
|
107
|
+
wait() {
|
|
108
|
+
if (this.#failure || this.#terminal || this.#updates.size > 0)
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
return new Promise((resolve) => { this.#wake = resolve; });
|
|
111
|
+
}
|
|
112
|
+
#notify() {
|
|
113
|
+
const wake = this.#wake;
|
|
114
|
+
this.#wake = null;
|
|
115
|
+
wake?.();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function nonEmpty(value, label) {
|
|
119
|
+
if (typeof value !== 'string' || !value.trim())
|
|
120
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
121
|
+
return value.trim();
|
|
122
|
+
}
|
|
123
|
+
function cleanTitle(value) {
|
|
124
|
+
const title = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
125
|
+
return title || 'New session';
|
|
126
|
+
}
|
|
127
|
+
function cleanCursor(value) {
|
|
128
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
129
|
+
}
|
|
130
|
+
function errorRecord(error, code = 'TURN_FAILED') {
|
|
131
|
+
if (error instanceof Error) {
|
|
132
|
+
const ownCode = error.code;
|
|
133
|
+
return Object.freeze({
|
|
134
|
+
code: typeof ownCode === 'string' && ownCode ? ownCode : code,
|
|
135
|
+
message: error.message,
|
|
136
|
+
name: error.name,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze({ code, message: String(error) });
|
|
140
|
+
}
|
|
141
|
+
function jsonCheckpoint(value) {
|
|
142
|
+
if (value === undefined || value === null)
|
|
143
|
+
return null;
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(JSON.stringify(value));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function durableUpdate(update, stored) {
|
|
152
|
+
const source = update;
|
|
153
|
+
const meta = source['_meta'] && typeof source['_meta'] === 'object' && !Array.isArray(source['_meta'])
|
|
154
|
+
? source['_meta']
|
|
155
|
+
: {};
|
|
156
|
+
const existing = meta['amalgm'] && typeof meta['amalgm'] === 'object' && !Array.isArray(meta['amalgm'])
|
|
157
|
+
? meta['amalgm']
|
|
158
|
+
: {};
|
|
159
|
+
return {
|
|
160
|
+
...source,
|
|
161
|
+
_meta: {
|
|
162
|
+
...meta,
|
|
163
|
+
amalgm: {
|
|
164
|
+
...existing,
|
|
165
|
+
conversationId: stored.conversationId,
|
|
166
|
+
turnId: stored.turnId,
|
|
167
|
+
sequence: stored.sequence,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export class ChatTurnStream {
|
|
173
|
+
sessionId;
|
|
174
|
+
turnId;
|
|
175
|
+
completion;
|
|
176
|
+
#subscribe;
|
|
177
|
+
constructor(sessionId, turnId, completion, subscribe) {
|
|
178
|
+
this.sessionId = sessionId;
|
|
179
|
+
this.turnId = turnId;
|
|
180
|
+
this.completion = completion;
|
|
181
|
+
this.#subscribe = subscribe;
|
|
182
|
+
}
|
|
183
|
+
reconnect(afterSequence = 0) {
|
|
184
|
+
return this.#subscribe(cleanCursor(afterSequence));
|
|
185
|
+
}
|
|
186
|
+
[Symbol.asyncIterator]() {
|
|
187
|
+
return this.#subscribe(0)[Symbol.asyncIterator]();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export class ChatSession {
|
|
191
|
+
id;
|
|
192
|
+
#chat;
|
|
193
|
+
constructor(chat, id) {
|
|
194
|
+
this.#chat = chat;
|
|
195
|
+
this.id = id;
|
|
196
|
+
}
|
|
197
|
+
send(options) {
|
|
198
|
+
return this.#chat.send(this.id, options);
|
|
199
|
+
}
|
|
200
|
+
reconnect(turnId, options = {}) {
|
|
201
|
+
return this.#chat.reconnectTurn(this.id, turnId, options.afterSequence);
|
|
202
|
+
}
|
|
203
|
+
interrupt() {
|
|
204
|
+
return this.#chat.interruptSession(this.id);
|
|
205
|
+
}
|
|
206
|
+
inspect() {
|
|
207
|
+
return this.#chat.inspectSession(this.id);
|
|
208
|
+
}
|
|
209
|
+
messages() {
|
|
210
|
+
return this.#chat.listMessages(this.id);
|
|
211
|
+
}
|
|
212
|
+
updateTitle(title) {
|
|
213
|
+
return this.#chat.updateTitle(this.id, title);
|
|
214
|
+
}
|
|
215
|
+
close() {
|
|
216
|
+
return this.#chat.closeSession(this.id);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export class Chat {
|
|
220
|
+
#runtime;
|
|
221
|
+
#ownerUserId;
|
|
222
|
+
#conversations;
|
|
223
|
+
#uuid;
|
|
224
|
+
#clockIso;
|
|
225
|
+
#sha256Hex;
|
|
226
|
+
#executions;
|
|
227
|
+
#subscriberBufferSize;
|
|
228
|
+
#journalBatch;
|
|
229
|
+
#sessions = new Map();
|
|
230
|
+
#turns = new Map();
|
|
231
|
+
#subscribers = new Map();
|
|
232
|
+
constructor(options) {
|
|
233
|
+
this.#runtime = options.runtime;
|
|
234
|
+
this.#ownerUserId = nonEmpty(options.ownerUserId, 'ownerUserId');
|
|
235
|
+
this.#conversations = options.conversations;
|
|
236
|
+
this.#uuid = options.uuid;
|
|
237
|
+
this.#clockIso = options.clockIso;
|
|
238
|
+
this.#sha256Hex = options.sha256Hex;
|
|
239
|
+
this.#executions = options.executions ?? new PreparedExecutionStore();
|
|
240
|
+
this.#subscriberBufferSize = Number.isFinite(options.subscriberBufferSize)
|
|
241
|
+
? Math.max(1, Math.floor(options.subscriberBufferSize))
|
|
242
|
+
: 1024;
|
|
243
|
+
this.#journalBatch = options.journalBatch;
|
|
244
|
+
}
|
|
245
|
+
/** Control-plane operation. Call while the user is composing, never from send. */
|
|
246
|
+
async prepareExecution(id, input) {
|
|
247
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
248
|
+
throw new TypeError('prepared execution id must be a non-empty string');
|
|
249
|
+
}
|
|
250
|
+
const contract = normalizeExecutionContract(input);
|
|
251
|
+
const revision = executionRevision(contract, this.#sha256Hex);
|
|
252
|
+
const current = this.#executions.current(id.trim());
|
|
253
|
+
if (current?.revision === revision)
|
|
254
|
+
return current;
|
|
255
|
+
const binding = await this.#runtime.prepareExecution(contract);
|
|
256
|
+
return this.#executions.publish({ id: id.trim(), revision, contract, binding });
|
|
257
|
+
}
|
|
258
|
+
async startSession(options) {
|
|
259
|
+
const execution = this.#executions.resolve(options.execution);
|
|
260
|
+
const sessionId = options.sessionId?.trim() || this.#uuid();
|
|
261
|
+
if (this.#sessions.has(sessionId))
|
|
262
|
+
throw new SessionAlreadyExistsError(sessionId);
|
|
263
|
+
const scope = this.#scope(execution);
|
|
264
|
+
await this.#conversations.createConversation({
|
|
265
|
+
...scope,
|
|
266
|
+
id: sessionId,
|
|
267
|
+
agentRevisionId: execution.contract.agent.revisionId,
|
|
268
|
+
adapterId: execution.contract.agent.adapterId,
|
|
269
|
+
title: cleanTitle(options.title),
|
|
270
|
+
origin: options.origin?.trim() || null,
|
|
271
|
+
originId: options.originId?.trim() || null,
|
|
272
|
+
lastExecutionComputerId: execution.contract.computerId,
|
|
273
|
+
metadata: Object.freeze({ ...(options.metadata || {}) }),
|
|
274
|
+
createdAt: this.#clockIso(),
|
|
275
|
+
});
|
|
276
|
+
const runtimeSession = await this.#runtime.startSession({ sessionId, execution });
|
|
277
|
+
this.#sessions.set(sessionId, {
|
|
278
|
+
id: sessionId,
|
|
279
|
+
scope,
|
|
280
|
+
runtimeSession,
|
|
281
|
+
execution,
|
|
282
|
+
busyTurnId: null,
|
|
283
|
+
});
|
|
284
|
+
return new ChatSession(this, sessionId);
|
|
285
|
+
}
|
|
286
|
+
async resumeSession(options) {
|
|
287
|
+
const execution = this.#executions.resolve(options.execution);
|
|
288
|
+
const existing = this.#sessions.get(options.sessionId);
|
|
289
|
+
if (existing) {
|
|
290
|
+
this.#assertInstallation(existing.scope, execution, options.sessionId);
|
|
291
|
+
return new ChatSession(this, existing.id);
|
|
292
|
+
}
|
|
293
|
+
const scope = this.#scope(execution);
|
|
294
|
+
const conversation = await this.#conversations.getConversation(scope, options.sessionId);
|
|
295
|
+
if (!conversation)
|
|
296
|
+
throw new SessionNotFoundError(options.sessionId);
|
|
297
|
+
if (!this.#runtime.resumeSession)
|
|
298
|
+
throw new SessionNotFoundError(options.sessionId);
|
|
299
|
+
const [checkpoint, messages] = await Promise.all([
|
|
300
|
+
this.#conversations.getProviderCheckpoint(scope, options.sessionId, execution.revision),
|
|
301
|
+
this.#conversations.listMessages(scope, options.sessionId),
|
|
302
|
+
]);
|
|
303
|
+
const runtimeSession = await this.#runtime.resumeSession({
|
|
304
|
+
sessionId: options.sessionId,
|
|
305
|
+
execution,
|
|
306
|
+
checkpoint: checkpoint?.checkpoint ?? null,
|
|
307
|
+
messages,
|
|
308
|
+
});
|
|
309
|
+
this.#sessions.set(options.sessionId, {
|
|
310
|
+
id: options.sessionId,
|
|
311
|
+
scope,
|
|
312
|
+
runtimeSession,
|
|
313
|
+
execution,
|
|
314
|
+
busyTurnId: null,
|
|
315
|
+
});
|
|
316
|
+
return new ChatSession(this, options.sessionId);
|
|
317
|
+
}
|
|
318
|
+
/** Restore a durable conversation using the exact execution of its latest turn. */
|
|
319
|
+
async resumeStoredSession(sessionId) {
|
|
320
|
+
const id = nonEmpty(sessionId, 'sessionId');
|
|
321
|
+
const active = this.#sessions.get(id);
|
|
322
|
+
if (active) {
|
|
323
|
+
const latestTurn = await this.#conversations.getLatestTurn(active.scope, id);
|
|
324
|
+
if (!latestTurn)
|
|
325
|
+
throw new SessionNotFoundError(id);
|
|
326
|
+
return Object.freeze({
|
|
327
|
+
session: new ChatSession(this, id),
|
|
328
|
+
execution: Object.freeze({ id: active.execution.id, revision: active.execution.revision }),
|
|
329
|
+
latestTurn,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
const found = await this.#conversations.getConversationForOwner(this.#ownerUserId, id, true);
|
|
333
|
+
if (!found)
|
|
334
|
+
throw new SessionNotFoundError(id);
|
|
335
|
+
const latestTurn = await this.#conversations.getLatestTurn(found.scope, id);
|
|
336
|
+
if (!latestTurn)
|
|
337
|
+
throw new SessionNotFoundError(id);
|
|
338
|
+
const execution = await this.prepareExecution(`conversation:${id}`, latestTurn.execution);
|
|
339
|
+
const session = await this.resumeSession({ sessionId: id, execution });
|
|
340
|
+
return Object.freeze({ session, execution, latestTurn });
|
|
341
|
+
}
|
|
342
|
+
async send(sessionId, options) {
|
|
343
|
+
const record = this.#sessions.get(sessionId);
|
|
344
|
+
if (!record)
|
|
345
|
+
throw new SessionNotFoundError(sessionId);
|
|
346
|
+
if (record.busyTurnId)
|
|
347
|
+
throw new SessionBusyError(sessionId);
|
|
348
|
+
const execution = this.#executions.resolve(options.execution);
|
|
349
|
+
this.#assertInstallation(record.scope, execution, sessionId);
|
|
350
|
+
const prompt = normalizePrompt(options.prompt);
|
|
351
|
+
const turnId = options.clientTurnId === undefined
|
|
352
|
+
? this.#uuid()
|
|
353
|
+
: nonEmpty(options.clientTurnId, 'clientTurnId');
|
|
354
|
+
const userMessageId = options.userMessageId === undefined
|
|
355
|
+
? this.#uuid()
|
|
356
|
+
: nonEmpty(options.userMessageId, 'userMessageId');
|
|
357
|
+
const assistantMessageId = options.assistantMessageId === undefined
|
|
358
|
+
? this.#uuid()
|
|
359
|
+
: nonEmpty(options.assistantMessageId, 'assistantMessageId');
|
|
360
|
+
await this.#conversations.beginTurn({
|
|
361
|
+
...record.scope,
|
|
362
|
+
conversationId: sessionId,
|
|
363
|
+
turnId,
|
|
364
|
+
userMessageId,
|
|
365
|
+
prompt,
|
|
366
|
+
execution: execution.contract,
|
|
367
|
+
createdAt: this.#clockIso(),
|
|
368
|
+
});
|
|
369
|
+
record.busyTurnId = turnId;
|
|
370
|
+
this.#turns.set(turnId, {
|
|
371
|
+
conversationId: sessionId,
|
|
372
|
+
scope: record.scope,
|
|
373
|
+
execution,
|
|
374
|
+
phase: 'running',
|
|
375
|
+
interruptAccepted: false,
|
|
376
|
+
interruptAttempt: null,
|
|
377
|
+
});
|
|
378
|
+
const completion = this.#pumpTurn(record, turnId, assistantMessageId, prompt, execution);
|
|
379
|
+
void completion.catch(() => { });
|
|
380
|
+
return new ChatTurnStream(sessionId, turnId, completion, (afterSequence) => this.reconnectTurn(sessionId, turnId, afterSequence));
|
|
381
|
+
}
|
|
382
|
+
reconnectTurn(sessionId, turnId, afterSequence = 0) {
|
|
383
|
+
const record = this.#sessions.get(sessionId);
|
|
384
|
+
if (!record)
|
|
385
|
+
throw new SessionNotFoundError(sessionId);
|
|
386
|
+
const context = this.#turns.get(turnId) || {
|
|
387
|
+
conversationId: sessionId,
|
|
388
|
+
scope: record.scope,
|
|
389
|
+
execution: record.execution,
|
|
390
|
+
phase: 'terminal',
|
|
391
|
+
interruptAccepted: false,
|
|
392
|
+
interruptAttempt: null,
|
|
393
|
+
};
|
|
394
|
+
if (context.conversationId !== sessionId)
|
|
395
|
+
throw new SessionNotFoundError(sessionId);
|
|
396
|
+
return this.#subscribe(context, turnId, cleanCursor(afterSequence));
|
|
397
|
+
}
|
|
398
|
+
async activeTurn(sessionId) {
|
|
399
|
+
const record = this.#requireSession(sessionId);
|
|
400
|
+
if (!record.busyTurnId)
|
|
401
|
+
return null;
|
|
402
|
+
return this.#conversations.getTurn(record.scope, record.busyTurnId);
|
|
403
|
+
}
|
|
404
|
+
async latestTurn(sessionId) {
|
|
405
|
+
const record = this.#requireSession(sessionId);
|
|
406
|
+
return this.#conversations.getLatestTurn(record.scope, sessionId);
|
|
407
|
+
}
|
|
408
|
+
async replayTurn(sessionId, turnId, afterSequence = 0) {
|
|
409
|
+
const record = this.#requireSession(sessionId);
|
|
410
|
+
const turn = await this.#conversations.getTurn(record.scope, turnId);
|
|
411
|
+
if (!turn || turn.conversationId !== sessionId)
|
|
412
|
+
throw new SessionNotFoundError(sessionId);
|
|
413
|
+
const updates = await this.#conversations.listTurnUpdates(record.scope, turnId, cleanCursor(afterSequence));
|
|
414
|
+
return Object.freeze({ turn, updates });
|
|
415
|
+
}
|
|
416
|
+
interruptSession(sessionId) {
|
|
417
|
+
const record = this.#requireSession(sessionId);
|
|
418
|
+
const turnId = record.busyTurnId;
|
|
419
|
+
if (!turnId)
|
|
420
|
+
return Promise.resolve(Object.freeze({ interrupted: false, turnId: null }));
|
|
421
|
+
const context = this.#turns.get(turnId);
|
|
422
|
+
if (!context || context.phase !== 'running') {
|
|
423
|
+
return Promise.resolve(Object.freeze({ interrupted: false, turnId: null }));
|
|
424
|
+
}
|
|
425
|
+
if (context.interruptAttempt)
|
|
426
|
+
return context.interruptAttempt;
|
|
427
|
+
if (!this.#runtime.interruptTurn)
|
|
428
|
+
throw new InterruptUnsupportedError(sessionId);
|
|
429
|
+
const attempt = Promise.resolve(this.#runtime.interruptTurn({
|
|
430
|
+
sessionId,
|
|
431
|
+
turnId,
|
|
432
|
+
session: record.runtimeSession,
|
|
433
|
+
execution: context.execution,
|
|
434
|
+
})).then(() => {
|
|
435
|
+
context.interruptAccepted = true;
|
|
436
|
+
return Object.freeze({ interrupted: true, turnId });
|
|
437
|
+
}).catch((error) => {
|
|
438
|
+
context.interruptAttempt = null;
|
|
439
|
+
throw error;
|
|
440
|
+
});
|
|
441
|
+
context.interruptAttempt = attempt;
|
|
442
|
+
return attempt;
|
|
443
|
+
}
|
|
444
|
+
async inspectSession(sessionId) {
|
|
445
|
+
const record = this.#requireSession(sessionId);
|
|
446
|
+
const conversation = await this.#conversations.getConversation(record.scope, sessionId);
|
|
447
|
+
if (!conversation)
|
|
448
|
+
throw new SessionNotFoundError(sessionId);
|
|
449
|
+
return conversation;
|
|
450
|
+
}
|
|
451
|
+
listConversations(options = {}) {
|
|
452
|
+
return this.#conversations.listConversations(this.#ownerUserId, options);
|
|
453
|
+
}
|
|
454
|
+
listMessages(sessionId) {
|
|
455
|
+
const record = this.#requireSession(sessionId);
|
|
456
|
+
return this.#conversations.listMessages(record.scope, sessionId);
|
|
457
|
+
}
|
|
458
|
+
updateTitle(sessionId, title) {
|
|
459
|
+
const record = this.#requireSession(sessionId);
|
|
460
|
+
return this.#conversations.updateTitle(record.scope, sessionId, cleanTitle(title), this.#clockIso());
|
|
461
|
+
}
|
|
462
|
+
markSeen(sessionId) {
|
|
463
|
+
const record = this.#requireSession(sessionId);
|
|
464
|
+
return this.#conversations.markSeen(record.scope, sessionId, this.#clockIso());
|
|
465
|
+
}
|
|
466
|
+
async archiveSession(sessionId) {
|
|
467
|
+
const record = this.#requireSession(sessionId);
|
|
468
|
+
if (record.busyTurnId)
|
|
469
|
+
throw new SessionBusyError(sessionId);
|
|
470
|
+
return this.#conversations.archiveConversation(record.scope, sessionId, this.#clockIso());
|
|
471
|
+
}
|
|
472
|
+
recoverInterruptedTurns() {
|
|
473
|
+
return this.#conversations.recoverInterrupted(this.#ownerUserId, this.#clockIso());
|
|
474
|
+
}
|
|
475
|
+
async closeSession(sessionId) {
|
|
476
|
+
const record = this.#requireSession(sessionId);
|
|
477
|
+
if (record.busyTurnId)
|
|
478
|
+
throw new SessionBusyError(sessionId);
|
|
479
|
+
await this.#runtime.closeSession?.(sessionId, record.runtimeSession);
|
|
480
|
+
this.#sessions.delete(sessionId);
|
|
481
|
+
}
|
|
482
|
+
async close() {
|
|
483
|
+
for (const record of this.#sessions.values()) {
|
|
484
|
+
if (record.busyTurnId)
|
|
485
|
+
throw new SessionBusyError(record.id);
|
|
486
|
+
}
|
|
487
|
+
for (const record of this.#sessions.values()) {
|
|
488
|
+
await this.#runtime.closeSession?.(record.id, record.runtimeSession);
|
|
489
|
+
}
|
|
490
|
+
this.#sessions.clear();
|
|
491
|
+
await this.#conversations.close?.();
|
|
492
|
+
}
|
|
493
|
+
async #pumpTurn(record, turnId, assistantMessageId, prompt, execution) {
|
|
494
|
+
const transcript = createAcpTranscriptState();
|
|
495
|
+
let status = 'completed';
|
|
496
|
+
let failure = null;
|
|
497
|
+
let providerCheckpoint = null;
|
|
498
|
+
let promptFailure = null;
|
|
499
|
+
try {
|
|
500
|
+
const source = await this.#runtime.prompt({
|
|
501
|
+
sessionId: record.id,
|
|
502
|
+
turnId,
|
|
503
|
+
session: record.runtimeSession,
|
|
504
|
+
prompt,
|
|
505
|
+
execution,
|
|
506
|
+
previousExecution: record.execution,
|
|
507
|
+
});
|
|
508
|
+
record.execution = execution;
|
|
509
|
+
for await (const updates of this.#batches(source)) {
|
|
510
|
+
const storedUpdates = await this.#conversations.appendTurnUpdates({
|
|
511
|
+
...record.scope,
|
|
512
|
+
conversationId: record.id,
|
|
513
|
+
turnId,
|
|
514
|
+
updates,
|
|
515
|
+
});
|
|
516
|
+
for (const stored of storedUpdates) {
|
|
517
|
+
applyAcpTranscriptUpdate(transcript, stored.update);
|
|
518
|
+
this.#announceUpdate(turnId, stored);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
providerCheckpoint = jsonCheckpoint(await this.#runtime.checkpointSession?.({
|
|
523
|
+
sessionId: record.id,
|
|
524
|
+
session: record.runtimeSession,
|
|
525
|
+
execution,
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
// A completed provider turn remains complete when optional native
|
|
530
|
+
// continuation capture fails. Canonical messages remain resumable.
|
|
531
|
+
providerCheckpoint = null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
promptFailure = error;
|
|
536
|
+
}
|
|
537
|
+
const context = this.#turns.get(turnId);
|
|
538
|
+
if (context) {
|
|
539
|
+
context.phase = 'sealing';
|
|
540
|
+
if (context.interruptAttempt) {
|
|
541
|
+
try {
|
|
542
|
+
await context.interruptAttempt;
|
|
543
|
+
}
|
|
544
|
+
catch { /* the prompt result decides below */ }
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (context?.interruptAccepted) {
|
|
548
|
+
status = 'cancelled';
|
|
549
|
+
}
|
|
550
|
+
else if (promptFailure !== null) {
|
|
551
|
+
status = 'failed';
|
|
552
|
+
failure = errorRecord(promptFailure);
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const turn = await this.#conversations.sealTurn({
|
|
556
|
+
...record.scope,
|
|
557
|
+
conversationId: record.id,
|
|
558
|
+
turnId,
|
|
559
|
+
assistantMessageId,
|
|
560
|
+
status,
|
|
561
|
+
assistantParts: finalizeAcpTranscript(transcript, status === 'failed'),
|
|
562
|
+
modelId: execution.contract.model.modelId,
|
|
563
|
+
error: failure,
|
|
564
|
+
providerCheckpoint: providerCheckpoint === null ? null : {
|
|
565
|
+
conversationId: record.id,
|
|
566
|
+
executionRevision: execution.revision,
|
|
567
|
+
adapterId: execution.contract.agent.adapterId,
|
|
568
|
+
checkpoint: providerCheckpoint,
|
|
569
|
+
updatedAt: this.#clockIso(),
|
|
570
|
+
},
|
|
571
|
+
completedAt: this.#clockIso(),
|
|
572
|
+
});
|
|
573
|
+
this.#announceTerminal(turnId, turn);
|
|
574
|
+
if (status === 'failed')
|
|
575
|
+
throw new TurnStreamError(turn);
|
|
576
|
+
return turn;
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
if (error instanceof TurnStreamError)
|
|
580
|
+
throw error;
|
|
581
|
+
const persistenceError = error instanceof Error ? error : new Error(String(error));
|
|
582
|
+
this.#failSubscribers(turnId, persistenceError);
|
|
583
|
+
throw persistenceError;
|
|
584
|
+
}
|
|
585
|
+
finally {
|
|
586
|
+
if (context)
|
|
587
|
+
context.phase = 'terminal';
|
|
588
|
+
if (record.busyTurnId === turnId)
|
|
589
|
+
record.busyTurnId = null;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async *#batches(source) {
|
|
593
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
594
|
+
let next = await iterator.next();
|
|
595
|
+
while (!next.done) {
|
|
596
|
+
const batch = [{ update: next.value, createdAt: this.#clockIso() }];
|
|
597
|
+
const policy = this.#journalBatch;
|
|
598
|
+
if (!policy || !this.#isContentDelta(next.value)) {
|
|
599
|
+
yield batch;
|
|
600
|
+
next = await iterator.next();
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const maximum = Number.isFinite(policy.maxUpdates)
|
|
604
|
+
? Math.max(1, Math.floor(policy.maxUpdates))
|
|
605
|
+
: 1;
|
|
606
|
+
const delay = Number.isFinite(policy.maxDelayMs)
|
|
607
|
+
? Math.max(0, Math.floor(policy.maxDelayMs))
|
|
608
|
+
: 0;
|
|
609
|
+
let pending = iterator.next();
|
|
610
|
+
let held = null;
|
|
611
|
+
while (batch.length < maximum) {
|
|
612
|
+
const race = await Promise.race([
|
|
613
|
+
pending.then((result) => ({ kind: 'next', result })),
|
|
614
|
+
policy.wait(delay).then(() => ({ kind: 'timeout' })),
|
|
615
|
+
]);
|
|
616
|
+
if (race.kind === 'timeout')
|
|
617
|
+
break;
|
|
618
|
+
if (race.result.done) {
|
|
619
|
+
held = race.result;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
if (!this.#isContentDelta(race.result.value)) {
|
|
623
|
+
held = race.result;
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
batch.push({ update: race.result.value, createdAt: this.#clockIso() });
|
|
627
|
+
pending = iterator.next();
|
|
628
|
+
}
|
|
629
|
+
yield batch;
|
|
630
|
+
next = held || await pending;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
#isContentDelta(update) {
|
|
634
|
+
const kind = update['sessionUpdate'];
|
|
635
|
+
return kind === 'agent_message_chunk' || kind === 'agent_thought_chunk';
|
|
636
|
+
}
|
|
637
|
+
async *#subscribe(context, turnId, afterSequence) {
|
|
638
|
+
const buffer = new LiveTurnBuffer(turnId, this.#subscriberBufferSize);
|
|
639
|
+
const subscribers = this.#subscribers.get(turnId) || new Set();
|
|
640
|
+
subscribers.add(buffer);
|
|
641
|
+
this.#subscribers.set(turnId, subscribers);
|
|
642
|
+
let cursor = afterSequence;
|
|
643
|
+
try {
|
|
644
|
+
while (true) {
|
|
645
|
+
const backlog = await this.#conversations.listTurnUpdates(context.scope, turnId, cursor);
|
|
646
|
+
for (const stored of backlog)
|
|
647
|
+
buffer.offer(stored, cursor);
|
|
648
|
+
let next = buffer.takeAfter(cursor);
|
|
649
|
+
while (next) {
|
|
650
|
+
cursor = next.sequence;
|
|
651
|
+
yield durableUpdate(next.update, next);
|
|
652
|
+
next = buffer.takeAfter(cursor);
|
|
653
|
+
}
|
|
654
|
+
if (buffer.failure)
|
|
655
|
+
throw buffer.failure;
|
|
656
|
+
const terminal = buffer.terminal || await this.#conversations.getTurn(context.scope, turnId);
|
|
657
|
+
if (terminal && terminal.status !== 'running') {
|
|
658
|
+
if (terminal.status === 'failed' || terminal.status === 'interrupted')
|
|
659
|
+
throw new TurnStreamError(terminal);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
await buffer.wait();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
finally {
|
|
666
|
+
subscribers.delete(buffer);
|
|
667
|
+
if (subscribers.size === 0)
|
|
668
|
+
this.#subscribers.delete(turnId);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
#announceUpdate(turnId, update) {
|
|
672
|
+
for (const subscriber of this.#subscribers.get(turnId) || [])
|
|
673
|
+
subscriber.offer(update, 0);
|
|
674
|
+
}
|
|
675
|
+
#announceTerminal(turnId, turn) {
|
|
676
|
+
for (const subscriber of this.#subscribers.get(turnId) || [])
|
|
677
|
+
subscriber.finish(turn);
|
|
678
|
+
}
|
|
679
|
+
#failSubscribers(turnId, error) {
|
|
680
|
+
for (const subscriber of this.#subscribers.get(turnId) || [])
|
|
681
|
+
subscriber.fail(error);
|
|
682
|
+
}
|
|
683
|
+
#scope(execution) {
|
|
684
|
+
return Object.freeze({
|
|
685
|
+
ownerUserId: this.#ownerUserId,
|
|
686
|
+
agentInstallationId: execution.contract.agent.installationId,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
#assertInstallation(scope, execution, sessionId) {
|
|
690
|
+
if (scope.agentInstallationId !== execution.contract.agent.installationId) {
|
|
691
|
+
throw new SessionAgentMismatchError(sessionId);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
#requireSession(sessionId) {
|
|
695
|
+
const record = this.#sessions.get(sessionId);
|
|
696
|
+
if (!record)
|
|
697
|
+
throw new SessionNotFoundError(sessionId);
|
|
698
|
+
return record;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
//# sourceMappingURL=chat.js.map
|