@maximem/synap-js-sdk 0.3.0 → 0.4.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/MIGRATING.md +147 -0
- package/README.md +95 -82
- package/bin/synap-js-sdk.js +45 -122
- package/dist/anticipation-cache-BgkqcTPJ.d.cts +180 -0
- package/dist/anticipation-cache-BgkqcTPJ.d.ts +180 -0
- package/dist/anticipation-cache-D5YHEPV3.js +10 -0
- package/dist/anticipation-cache-D5YHEPV3.js.map +1 -0
- package/dist/anticipation-cache-JBZHTJ3O.cjs +10 -0
- package/dist/anticipation-cache-JBZHTJ3O.cjs.map +1 -0
- package/dist/chunk-5SC3ELNK.js +856 -0
- package/dist/chunk-5SC3ELNK.js.map +1 -0
- package/dist/chunk-HR5Y3KXS.cjs +172 -0
- package/dist/chunk-HR5Y3KXS.cjs.map +1 -0
- package/dist/chunk-NIDPTBNC.cjs +800 -0
- package/dist/chunk-NIDPTBNC.cjs.map +1 -0
- package/dist/chunk-QBCVMPGK.js +28 -0
- package/dist/chunk-QBCVMPGK.js.map +1 -0
- package/dist/chunk-T3NZBIV2.js +172 -0
- package/dist/chunk-T3NZBIV2.js.map +1 -0
- package/dist/chunk-TCEP5OVR.js +800 -0
- package/dist/chunk-TCEP5OVR.js.map +1 -0
- package/dist/chunk-VXRCBMCY.cjs +856 -0
- package/dist/chunk-VXRCBMCY.cjs.map +1 -0
- package/dist/chunk-ZKOJDUN4.cjs +28 -0
- package/dist/chunk-ZKOJDUN4.cjs.map +1 -0
- package/dist/env-55ULHNJZ.cjs +11 -0
- package/dist/env-55ULHNJZ.cjs.map +1 -0
- package/dist/env-QCUKKW36.js +11 -0
- package/dist/env-QCUKKW36.js.map +1 -0
- package/dist/grpc/index.cjs +87 -0
- package/dist/grpc/index.cjs.map +1 -0
- package/dist/grpc/index.d.cts +162 -0
- package/dist/grpc/index.d.ts +162 -0
- package/dist/grpc/index.js +87 -0
- package/dist/grpc/index.js.map +1 -0
- package/dist/index.cjs +2111 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1477 -0
- package/dist/index.d.ts +1477 -0
- package/dist/index.js +2111 -0
- package/dist/index.js.map +1 -0
- package/dist/stream-client-ME5N5NK4.js +7 -0
- package/dist/stream-client-ME5N5NK4.js.map +1 -0
- package/dist/stream-client-ZN6ADZBR.cjs +7 -0
- package/dist/stream-client-ZN6ADZBR.cjs.map +1 -0
- package/package.json +68 -23
- package/.env.example +0 -39
- package/bridge/synap_bridge.py +0 -768
- package/src/bridge-manager.js +0 -262
- package/src/errors.js +0 -168
- package/src/index.js +0 -13
- package/src/runtime.js +0 -161
- package/src/setup-typescript.js +0 -161
- package/src/synap-client.js +0 -464
- package/types/index.d.ts +0 -475
package/dist/index.js
ADDED
|
@@ -0,0 +1,2111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentUnavailableError,
|
|
3
|
+
AuthenticationError,
|
|
4
|
+
ConflictError,
|
|
5
|
+
ConnectionError,
|
|
6
|
+
ContextNotFoundError,
|
|
7
|
+
InsufficientCreditsError,
|
|
8
|
+
InvalidConversationIdError,
|
|
9
|
+
InvalidInputError,
|
|
10
|
+
InvalidInstanceIdError,
|
|
11
|
+
ListeningAlreadyActiveError,
|
|
12
|
+
ListeningNotActiveError,
|
|
13
|
+
NetworkTimeoutError,
|
|
14
|
+
PermanentError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
SDKError,
|
|
17
|
+
ServiceUnavailableError,
|
|
18
|
+
SessionExpiredError,
|
|
19
|
+
SynapError,
|
|
20
|
+
SynapPermanentError,
|
|
21
|
+
SynapTransientError,
|
|
22
|
+
TranscriptConflictError,
|
|
23
|
+
TransientError,
|
|
24
|
+
isSynapError
|
|
25
|
+
} from "./chunk-T3NZBIV2.js";
|
|
26
|
+
import {
|
|
27
|
+
ANY_SCOPE,
|
|
28
|
+
AnticipationCache,
|
|
29
|
+
BM25,
|
|
30
|
+
CONTRACT,
|
|
31
|
+
THRESHOLDS,
|
|
32
|
+
isRecallQuery,
|
|
33
|
+
stem,
|
|
34
|
+
tokenize
|
|
35
|
+
} from "./chunk-TCEP5OVR.js";
|
|
36
|
+
import {
|
|
37
|
+
getEnv
|
|
38
|
+
} from "./chunk-QBCVMPGK.js";
|
|
39
|
+
|
|
40
|
+
// src/util/correlation.ts
|
|
41
|
+
function newCorrelationId() {
|
|
42
|
+
const c = globalThis.crypto;
|
|
43
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
44
|
+
if (typeof c?.getRandomValues === "function") {
|
|
45
|
+
const b = c.getRandomValues(new Uint8Array(16));
|
|
46
|
+
b[6] = (b[6] ?? 0) & 15 | 64;
|
|
47
|
+
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
48
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
49
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
50
|
+
}
|
|
51
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
52
|
+
const r = Math.random() * 16 | 0;
|
|
53
|
+
return (ch === "x" ? r : r & 3 | 8).toString(16);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/transport/endpoints.ts
|
|
58
|
+
var ENDPOINTS = {
|
|
59
|
+
whoami: { method: "GET", path: "/api/v1/auth/whoami", idempotent: true },
|
|
60
|
+
memories_create: { method: "POST", path: "/api/v1/memories/create", idempotent: false },
|
|
61
|
+
memories_batch: { method: "POST", path: "/api/v1/memories/batch", idempotent: false },
|
|
62
|
+
memories_upload: { method: "POST", path: "/api/v1/memories/upload", idempotent: false },
|
|
63
|
+
memories_status: { method: "GET", path: "/api/v1/memories/status/{ingestion_id}", idempotent: true },
|
|
64
|
+
memories_get: { method: "GET", path: "/api/v1/memories/{memory_id}", idempotent: true },
|
|
65
|
+
memories_update: { method: "PATCH", path: "/api/v1/memories/{memory_id}", idempotent: false },
|
|
66
|
+
memories_delete: { method: "DELETE", path: "/api/v1/memories/{memory_id}", idempotent: true },
|
|
67
|
+
context_fetch_user: { method: "POST", path: "/v1/context/user/fetch", idempotent: true },
|
|
68
|
+
context_fetch_customer: { method: "POST", path: "/v1/context/customer/fetch", idempotent: true },
|
|
69
|
+
context_fetch_client: { method: "POST", path: "/v1/context/client/fetch", idempotent: true },
|
|
70
|
+
context_fetch_conversation: { method: "POST", path: "/v1/context/conversation/fetch", idempotent: true },
|
|
71
|
+
context_for_prompt: { method: "GET", path: "/v1/conversations/{conversation_id}/context-for-prompt", idempotent: true },
|
|
72
|
+
conversations_ingest: { method: "POST", path: "/v1/conversations/ingest", idempotent: false },
|
|
73
|
+
conversations_messages: { method: "POST", path: "/v1/conversations/messages", idempotent: false },
|
|
74
|
+
conversations_messages_batch: { method: "POST", path: "/v1/conversations/messages/batch", idempotent: false },
|
|
75
|
+
conversations_compact: { method: "POST", path: "/v1/conversations/compact", idempotent: false },
|
|
76
|
+
conversations_compacted: { method: "GET", path: "/v1/conversations/{conversation_id}/compacted", idempotent: true },
|
|
77
|
+
conversations_compaction_status: { method: "GET", path: "/v1/conversations/{conversation_id}/compaction/status", idempotent: true },
|
|
78
|
+
user_profile: { method: "GET", path: "/v1/users/{user_id}/profile", idempotent: true },
|
|
79
|
+
credits_balance: { method: "GET", path: "/v1/credits/balance", idempotent: true },
|
|
80
|
+
credits_ledger: { method: "GET", path: "/v1/credits/ledger", idempotent: true },
|
|
81
|
+
credits_estimate: { method: "POST", path: "/v1/credits/estimate", idempotent: true },
|
|
82
|
+
credits_redeem: { method: "POST", path: "/v1/credits/redeem", idempotent: false },
|
|
83
|
+
telemetry_batch: { method: "POST", path: "/v1/telemetry/batch", idempotent: false }
|
|
84
|
+
};
|
|
85
|
+
function resolvePath(name, params = {}) {
|
|
86
|
+
return ENDPOINTS[name].path.replace(/\{(\w+)\}/g, (_m, key) => {
|
|
87
|
+
const value = params[key];
|
|
88
|
+
if (value === void 0 || value === null || value === "") {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Endpoint ${name} requires path parameter '${key}'.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return encodeURIComponent(String(value));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/transport/retry.ts
|
|
98
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
99
|
+
"GET",
|
|
100
|
+
"HEAD",
|
|
101
|
+
"OPTIONS",
|
|
102
|
+
"PUT",
|
|
103
|
+
"DELETE"
|
|
104
|
+
]);
|
|
105
|
+
var DEFAULT_RETRY_POLICY = {
|
|
106
|
+
maxAttempts: 3,
|
|
107
|
+
backoffBase: 1,
|
|
108
|
+
backoffMax: 10,
|
|
109
|
+
backoffJitter: true
|
|
110
|
+
};
|
|
111
|
+
function shouldRetry(input) {
|
|
112
|
+
const { error, attempt, policy, method, outcomeUnknown } = input;
|
|
113
|
+
if (attempt >= policy.maxAttempts) {
|
|
114
|
+
return { retry: false, delaySeconds: 0, reason: "max_attempts_exhausted" };
|
|
115
|
+
}
|
|
116
|
+
if (!isSynapError(error) || !error.transient) {
|
|
117
|
+
return { retry: false, delaySeconds: 0, reason: "not_transient" };
|
|
118
|
+
}
|
|
119
|
+
const idempotent = input.idempotent ?? IDEMPOTENT_METHODS.has(method.toUpperCase());
|
|
120
|
+
if (outcomeUnknown && !idempotent) {
|
|
121
|
+
return {
|
|
122
|
+
retry: false,
|
|
123
|
+
delaySeconds: 0,
|
|
124
|
+
reason: "non_idempotent_outcome_unknown"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
retry: true,
|
|
129
|
+
delaySeconds: backoffDelay(error, attempt, policy),
|
|
130
|
+
reason: "transient_retryable"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function backoffDelay(error, attempt, policy) {
|
|
134
|
+
if (error instanceof RateLimitError && error.retryAfterSeconds != null) {
|
|
135
|
+
return Math.min(error.retryAfterSeconds, policy.backoffMax);
|
|
136
|
+
}
|
|
137
|
+
const exponential = policy.backoffBase * 2 ** (attempt - 1);
|
|
138
|
+
const capped = Math.min(exponential, policy.backoffMax);
|
|
139
|
+
if (!policy.backoffJitter) return capped;
|
|
140
|
+
return Math.random() * capped;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/transport/keepalive.ts
|
|
144
|
+
var KEEPALIVE_EXPIRY_MS = 3e5;
|
|
145
|
+
var HEARTBEAT_INTERVAL_MS = 24e4;
|
|
146
|
+
var MAX_CONNECTIONS = 20;
|
|
147
|
+
var cached;
|
|
148
|
+
function supportsDispatcher() {
|
|
149
|
+
return typeof process !== "undefined" && process.versions?.node !== void 0 && // Edge builds of Next expose a partial `process` shim, but no real Node.
|
|
150
|
+
typeof globalThis.EdgeRuntime === "undefined";
|
|
151
|
+
}
|
|
152
|
+
async function getKeepAliveDispatcher(options = {}) {
|
|
153
|
+
if (cached !== void 0) return cached;
|
|
154
|
+
if (!supportsDispatcher()) {
|
|
155
|
+
cached = null;
|
|
156
|
+
return cached;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const undici = await import(
|
|
160
|
+
/* @vite-ignore */
|
|
161
|
+
"undici"
|
|
162
|
+
);
|
|
163
|
+
cached = new undici.Agent({
|
|
164
|
+
keepAliveTimeout: options.keepAliveExpiryMs ?? KEEPALIVE_EXPIRY_MS,
|
|
165
|
+
// undici will not honour a keepAliveTimeout above this ceiling.
|
|
166
|
+
keepAliveMaxTimeout: options.keepAliveExpiryMs ?? KEEPALIVE_EXPIRY_MS,
|
|
167
|
+
connections: options.maxConnections ?? MAX_CONNECTIONS
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
cached = null;
|
|
171
|
+
}
|
|
172
|
+
return cached;
|
|
173
|
+
}
|
|
174
|
+
var Heartbeat = class {
|
|
175
|
+
constructor(ping, intervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
176
|
+
this.ping = ping;
|
|
177
|
+
this.intervalMs = intervalMs;
|
|
178
|
+
}
|
|
179
|
+
ping;
|
|
180
|
+
intervalMs;
|
|
181
|
+
timer = null;
|
|
182
|
+
start() {
|
|
183
|
+
if (this.timer !== null) return;
|
|
184
|
+
if (this.intervalMs <= 0) return;
|
|
185
|
+
if (typeof setInterval !== "function") return;
|
|
186
|
+
this.timer = setInterval(() => {
|
|
187
|
+
void Promise.resolve(this.ping()).catch(() => {
|
|
188
|
+
});
|
|
189
|
+
}, this.intervalMs);
|
|
190
|
+
this.timer.unref?.();
|
|
191
|
+
}
|
|
192
|
+
stop() {
|
|
193
|
+
if (this.timer !== null) {
|
|
194
|
+
clearInterval(this.timer);
|
|
195
|
+
this.timer = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
get running() {
|
|
199
|
+
return this.timer !== null;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/transport/http.ts
|
|
204
|
+
var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
|
|
205
|
+
var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
|
|
206
|
+
var HttpTransport = class {
|
|
207
|
+
baseUrl;
|
|
208
|
+
// Mutable so `configure()` can adjust them before the first request, and so
|
|
209
|
+
// `initialize()` can write back the identity resolved from whoami.
|
|
210
|
+
timeouts;
|
|
211
|
+
retryPolicy;
|
|
212
|
+
#credentials;
|
|
213
|
+
userAgent;
|
|
214
|
+
fetchImpl;
|
|
215
|
+
keepAliveOptions;
|
|
216
|
+
heartbeat;
|
|
217
|
+
closed = false;
|
|
218
|
+
constructor(options) {
|
|
219
|
+
this.#credentials = options.credentials;
|
|
220
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
221
|
+
this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
|
|
222
|
+
this.retryPolicy = { ...DEFAULT_RETRY_POLICY, ...options.retryPolicy };
|
|
223
|
+
this.userAgent = options.userAgent ?? "@maximem/synap-js-sdk";
|
|
224
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
225
|
+
this.keepAliveOptions = options.keepAlive ?? {};
|
|
226
|
+
this.heartbeat = new Heartbeat(
|
|
227
|
+
() => this.pingHealth(),
|
|
228
|
+
options.keepAlive?.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
/** Start keeping the connection warm. Opt-in: it is pointless in serverless. */
|
|
232
|
+
startHeartbeat() {
|
|
233
|
+
if (!this.closed) this.heartbeat.start();
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Release timers. Safe to call more than once.
|
|
237
|
+
*
|
|
238
|
+
* This is the supported shutdown path, deliberately explicit rather than
|
|
239
|
+
* hooked to `process.on('exit')`: those handlers leak a listener per client,
|
|
240
|
+
* never fire in Lambda, and `process` does not exist in Workers (gotcha G-I).
|
|
241
|
+
*/
|
|
242
|
+
/**
|
|
243
|
+
* A COPY of the current credentials.
|
|
244
|
+
*
|
|
245
|
+
* A copy, not the live object: handing out the internal reference is how
|
|
246
|
+
* `client.transport.credentials.apiKey` became readable and mutable from
|
|
247
|
+
* outside in the first place.
|
|
248
|
+
*/
|
|
249
|
+
currentCredentials() {
|
|
250
|
+
return { ...this.#credentials };
|
|
251
|
+
}
|
|
252
|
+
/** Write back identity resolved from `GET /api/v1/auth/whoami`. */
|
|
253
|
+
updateCredentials(patch) {
|
|
254
|
+
this.#credentials = { ...this.#credentials, ...patch };
|
|
255
|
+
}
|
|
256
|
+
setTimeouts(timeouts) {
|
|
257
|
+
this.timeouts = { ...this.timeouts, ...timeouts };
|
|
258
|
+
}
|
|
259
|
+
/** `null` restores the default policy, matching Python's `retry_policy=None`. */
|
|
260
|
+
setRetryPolicy(policy) {
|
|
261
|
+
this.retryPolicy = policy === null ? { ...DEFAULT_RETRY_POLICY } : { ...this.retryPolicy, ...policy };
|
|
262
|
+
}
|
|
263
|
+
close() {
|
|
264
|
+
this.closed = true;
|
|
265
|
+
this.heartbeat.stop();
|
|
266
|
+
}
|
|
267
|
+
async request(name, options = {}) {
|
|
268
|
+
const spec = ENDPOINTS[name];
|
|
269
|
+
const correlationId = options.correlationId ?? newCorrelationId();
|
|
270
|
+
const idempotent = options.idempotent ?? spec.idempotent;
|
|
271
|
+
let attempt = 0;
|
|
272
|
+
let last = null;
|
|
273
|
+
while (attempt < this.retryPolicy.maxAttempts) {
|
|
274
|
+
attempt += 1;
|
|
275
|
+
try {
|
|
276
|
+
return await this.attempt(name, options, correlationId);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
last = { error, outcomeUnknown: isOutcomeUnknown(error) };
|
|
279
|
+
const decision = shouldRetry({
|
|
280
|
+
error,
|
|
281
|
+
attempt,
|
|
282
|
+
policy: this.retryPolicy,
|
|
283
|
+
method: spec.method,
|
|
284
|
+
outcomeUnknown: last.outcomeUnknown,
|
|
285
|
+
idempotent
|
|
286
|
+
});
|
|
287
|
+
if (!decision.retry) throw error;
|
|
288
|
+
await sleep(decision.delaySeconds * 1e3, options.signal);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
throw last?.error ?? new SynapError("Request failed with no recorded error");
|
|
292
|
+
}
|
|
293
|
+
async attempt(name, options, correlationId) {
|
|
294
|
+
const spec = ENDPOINTS[name];
|
|
295
|
+
const url = this.buildUrl(resolvePath(name, options.pathParams), options.query);
|
|
296
|
+
const dispatcher = await getKeepAliveDispatcher(this.keepAliveOptions);
|
|
297
|
+
const signal = withTimeout(this.timeouts.read * 1e3, options.signal);
|
|
298
|
+
const init = {
|
|
299
|
+
method: spec.method,
|
|
300
|
+
headers: this.buildHeaders(correlationId, { json: options.rawBody !== true }),
|
|
301
|
+
signal: signal.signal
|
|
302
|
+
};
|
|
303
|
+
if (options.body !== void 0 && spec.method !== "GET") {
|
|
304
|
+
init.body = options.rawBody === true ? options.body : JSON.stringify(options.body);
|
|
305
|
+
}
|
|
306
|
+
if (dispatcher) init["dispatcher"] = dispatcher;
|
|
307
|
+
let response;
|
|
308
|
+
try {
|
|
309
|
+
response = await this.fetchImpl(url, init);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
throw toTransportError(error, correlationId);
|
|
312
|
+
} finally {
|
|
313
|
+
signal.dispose();
|
|
314
|
+
}
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
throw await toHttpError(response, correlationId);
|
|
317
|
+
}
|
|
318
|
+
if (response.status === 204) return void 0;
|
|
319
|
+
const text = await response.text();
|
|
320
|
+
if (!text) return void 0;
|
|
321
|
+
try {
|
|
322
|
+
return JSON.parse(text);
|
|
323
|
+
} catch {
|
|
324
|
+
throw new SynapError(`Malformed JSON in response from ${url}`, { correlationId });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
buildUrl(path, query) {
|
|
328
|
+
const url = new URL(this.baseUrl + path);
|
|
329
|
+
for (const [k, v] of Object.entries(query ?? {})) {
|
|
330
|
+
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
331
|
+
}
|
|
332
|
+
return url.toString();
|
|
333
|
+
}
|
|
334
|
+
buildHeaders(correlationId, options = {}) {
|
|
335
|
+
return {
|
|
336
|
+
Authorization: `Bearer ${this.#credentials.apiKey}`,
|
|
337
|
+
"X-Client-ID": this.#credentials.clientId,
|
|
338
|
+
"X-Instance-ID": this.#credentials.instanceId,
|
|
339
|
+
"X-Correlation-ID": correlationId,
|
|
340
|
+
...options.json === false ? {} : { "Content-Type": "application/json" },
|
|
341
|
+
"User-Agent": this.userAgent
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/** Unauthenticated and unmetered, exactly like the Python heartbeat. */
|
|
345
|
+
async pingHealth() {
|
|
346
|
+
const signal = withTimeout(1e4);
|
|
347
|
+
try {
|
|
348
|
+
const dispatcher = await getKeepAliveDispatcher(this.keepAliveOptions);
|
|
349
|
+
const init = { signal: signal.signal };
|
|
350
|
+
if (dispatcher) init["dispatcher"] = dispatcher;
|
|
351
|
+
await this.fetchImpl(`${this.baseUrl}/health`, init);
|
|
352
|
+
} finally {
|
|
353
|
+
signal.dispose();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
function isOutcomeUnknown(error) {
|
|
358
|
+
if (error instanceof RateLimitError) return false;
|
|
359
|
+
if (error instanceof ServiceUnavailableError) return false;
|
|
360
|
+
if (error instanceof AuthenticationError) return false;
|
|
361
|
+
if (error instanceof InvalidInputError) return false;
|
|
362
|
+
if (error instanceof ConflictError) return false;
|
|
363
|
+
if (error instanceof InsufficientCreditsError) return false;
|
|
364
|
+
if (error instanceof ContextNotFoundError) return false;
|
|
365
|
+
if (error instanceof NetworkTimeoutError) return true;
|
|
366
|
+
if (isConnectionRefusal(error)) return false;
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
function isConnectionRefusal(error) {
|
|
370
|
+
const code = error?.cause?.code ?? error?.code;
|
|
371
|
+
return code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EAI_AGAIN" || code === "ECONNRESET" || code === "EHOSTUNREACH";
|
|
372
|
+
}
|
|
373
|
+
function toTransportError(error, correlationId) {
|
|
374
|
+
if (error instanceof SynapError) return error;
|
|
375
|
+
const name = error?.name;
|
|
376
|
+
if (name === "AbortError" || name === "TimeoutError") {
|
|
377
|
+
return new NetworkTimeoutError("Request timed out", { correlationId, cause: error });
|
|
378
|
+
}
|
|
379
|
+
return new ServiceUnavailableError(
|
|
380
|
+
`Network request failed: ${error?.message ?? String(error)}`,
|
|
381
|
+
{ correlationId, cause: error }
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
async function toHttpError(response, correlationId) {
|
|
385
|
+
const body = await response.text().catch(() => "");
|
|
386
|
+
let parsed = {};
|
|
387
|
+
try {
|
|
388
|
+
parsed = JSON.parse(body);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
const rawDetail = parsed["detail"] ?? parsed["message"] ?? body ?? "";
|
|
392
|
+
const detailObject = typeof rawDetail === "object" && rawDetail !== null ? rawDetail : null;
|
|
393
|
+
const detail = detailObject !== null ? String(detailObject["message"] ?? JSON.stringify(detailObject)) : String(rawDetail);
|
|
394
|
+
const message = `HTTP ${response.status}: ${detail || response.statusText}`;
|
|
395
|
+
const opts = { correlationId };
|
|
396
|
+
switch (response.status) {
|
|
397
|
+
case 400:
|
|
398
|
+
case 422:
|
|
399
|
+
return new InvalidInputError(message, opts);
|
|
400
|
+
case 401:
|
|
401
|
+
case 403:
|
|
402
|
+
return new AuthenticationError(message, opts);
|
|
403
|
+
case 404:
|
|
404
|
+
return new ContextNotFoundError(message, opts);
|
|
405
|
+
case 402:
|
|
406
|
+
return new InsufficientCreditsError(message, {
|
|
407
|
+
...opts,
|
|
408
|
+
balanceCredits: numberOrNull(parsed["balance_credits"]),
|
|
409
|
+
requiredCredits: numberOrNull(parsed["required_credits"])
|
|
410
|
+
});
|
|
411
|
+
case 409:
|
|
412
|
+
if (detailObject !== null && detailObject["code"] === "transcript_conflict") {
|
|
413
|
+
return new TranscriptConflictError(message, opts);
|
|
414
|
+
}
|
|
415
|
+
return new ConflictError(message, opts);
|
|
416
|
+
case 429: {
|
|
417
|
+
const header = response.headers.get("retry-after");
|
|
418
|
+
const retryAfter = header !== null && header.trim() !== "" ? Number(header) : null;
|
|
419
|
+
return new RateLimitError(message, {
|
|
420
|
+
...opts,
|
|
421
|
+
retryAfterSeconds: retryAfter !== null && Number.isFinite(retryAfter) ? retryAfter : null
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
case 503:
|
|
425
|
+
return new ServiceUnavailableError(message, opts);
|
|
426
|
+
case 504:
|
|
427
|
+
return new AgentUnavailableError(message, opts);
|
|
428
|
+
default:
|
|
429
|
+
if (response.status >= 500) return new ServiceUnavailableError(message, opts);
|
|
430
|
+
return new SynapError(message, opts);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function numberOrNull(v) {
|
|
434
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
435
|
+
}
|
|
436
|
+
function withTimeout(ms, external) {
|
|
437
|
+
const controller = new AbortController();
|
|
438
|
+
const onExternalAbort = () => controller.abort(external?.reason);
|
|
439
|
+
const timer = setTimeout(() => {
|
|
440
|
+
controller.abort(new NetworkTimeoutError(`Timed out after ${ms}ms`));
|
|
441
|
+
}, ms);
|
|
442
|
+
timer.unref?.();
|
|
443
|
+
if (external) {
|
|
444
|
+
if (external.aborted) onExternalAbort();
|
|
445
|
+
else external.addEventListener("abort", onExternalAbort, { once: true });
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
signal: controller.signal,
|
|
449
|
+
dispose() {
|
|
450
|
+
clearTimeout(timer);
|
|
451
|
+
external?.removeEventListener("abort", onExternalAbort);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function sleep(ms, signal) {
|
|
456
|
+
if (ms <= 0) return Promise.resolve();
|
|
457
|
+
return new Promise((resolve, reject) => {
|
|
458
|
+
const timer = setTimeout(() => {
|
|
459
|
+
signal?.removeEventListener("abort", onAbort);
|
|
460
|
+
resolve();
|
|
461
|
+
}, ms);
|
|
462
|
+
function onAbort() {
|
|
463
|
+
clearTimeout(timer);
|
|
464
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
465
|
+
}
|
|
466
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/context/fetch.ts
|
|
471
|
+
var ENDPOINT_BY_SCOPE = {
|
|
472
|
+
user: "context_fetch_user",
|
|
473
|
+
customer: "context_fetch_customer",
|
|
474
|
+
client: "context_fetch_client",
|
|
475
|
+
conversation: "context_fetch_conversation"
|
|
476
|
+
};
|
|
477
|
+
function opt(options, snake, camel) {
|
|
478
|
+
const a = options[snake];
|
|
479
|
+
if (a !== void 0) return a;
|
|
480
|
+
const b = options[camel];
|
|
481
|
+
return b === void 0 ? void 0 : b;
|
|
482
|
+
}
|
|
483
|
+
function buildFetchRequest(scope, options) {
|
|
484
|
+
const userId = opt(options, "user_id", "userId");
|
|
485
|
+
const customerId = opt(options, "customer_id", "customerId");
|
|
486
|
+
const conversationId = opt(options, "conversation_id", "conversationId");
|
|
487
|
+
const searchQuery = opt(options, "search_query", "searchQuery");
|
|
488
|
+
const maxResults = opt(options, "max_results", "maxResults") ?? 10;
|
|
489
|
+
const precisionLevel = opt(options, "precision_level", "precisionLevel") ?? "high";
|
|
490
|
+
const mode = options.mode ?? "fast";
|
|
491
|
+
if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
|
|
492
|
+
throw new InvalidInputError("search_query must be an array when provided");
|
|
493
|
+
}
|
|
494
|
+
if (options.types !== void 0 && !Array.isArray(options.types)) {
|
|
495
|
+
throw new InvalidInputError("types must be an array when provided");
|
|
496
|
+
}
|
|
497
|
+
const body = {
|
|
498
|
+
conversation_id: conversationId ?? null,
|
|
499
|
+
search_query: searchQuery ?? [],
|
|
500
|
+
max_results: maxResults,
|
|
501
|
+
types: options.types ?? ["all"],
|
|
502
|
+
mode
|
|
503
|
+
};
|
|
504
|
+
if (precisionLevel !== "high") body["precision_level"] = precisionLevel;
|
|
505
|
+
if (options.include_conversation_context === false) {
|
|
506
|
+
body["include_conversation_context"] = false;
|
|
507
|
+
}
|
|
508
|
+
switch (scope) {
|
|
509
|
+
case "user":
|
|
510
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
511
|
+
body["user_id"] = userId;
|
|
512
|
+
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
513
|
+
break;
|
|
514
|
+
case "customer":
|
|
515
|
+
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
516
|
+
body["customer_id"] = customerId;
|
|
517
|
+
break;
|
|
518
|
+
case "conversation":
|
|
519
|
+
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
520
|
+
if (userId !== void 0) body["user_id"] = userId;
|
|
521
|
+
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
522
|
+
break;
|
|
523
|
+
case "client":
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
return { endpoint: ENDPOINT_BY_SCOPE[scope], body };
|
|
527
|
+
}
|
|
528
|
+
async function fetchContext(transport, scope, options = {}) {
|
|
529
|
+
const { endpoint, body } = buildFetchRequest(scope, options);
|
|
530
|
+
const result = await transport.request(endpoint, { body });
|
|
531
|
+
return result?.["context"] ?? {};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/context/normalize.ts
|
|
535
|
+
function pickDefined(...values) {
|
|
536
|
+
for (const v of values) if (v !== void 0) return v;
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
function pick(...values) {
|
|
540
|
+
return pickDefined(...values);
|
|
541
|
+
}
|
|
542
|
+
var obj = (v) => v ?? {};
|
|
543
|
+
function normalizeTemporalFields(item = {}) {
|
|
544
|
+
return {
|
|
545
|
+
eventDate: pick(item["eventDate"], item.event_date, null),
|
|
546
|
+
validUntil: pick(item["validUntil"], item.valid_until, null),
|
|
547
|
+
temporalCategory: pick(item["temporalCategory"], item.temporal_category, null),
|
|
548
|
+
temporalConfidence: pick(item["temporalConfidence"], item.temporal_confidence, 0)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function normalizeFact(item = {}) {
|
|
552
|
+
return {
|
|
553
|
+
id: item.id || "",
|
|
554
|
+
content: item.content || "",
|
|
555
|
+
confidence: pick(item.confidence, 0),
|
|
556
|
+
source: item.source || "",
|
|
557
|
+
extractedAt: pick(item["extractedAt"], item.extracted_at, null),
|
|
558
|
+
metadata: obj(item.metadata),
|
|
559
|
+
...normalizeTemporalFields(item)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function normalizePreference(item = {}) {
|
|
563
|
+
return {
|
|
564
|
+
id: item.id || "",
|
|
565
|
+
category: item["category"] || "",
|
|
566
|
+
content: item.content || "",
|
|
567
|
+
strength: pick(item["strength"], item.confidence, 0),
|
|
568
|
+
source: item.source || "",
|
|
569
|
+
extractedAt: pick(item["extractedAt"], item.extracted_at, null),
|
|
570
|
+
metadata: obj(item.metadata),
|
|
571
|
+
...normalizeTemporalFields(item)
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function normalizeEpisode(item = {}) {
|
|
575
|
+
return {
|
|
576
|
+
id: item.id || "",
|
|
577
|
+
summary: pick(item["summary"], item.content, ""),
|
|
578
|
+
occurredAt: pick(item["occurredAt"], item["occurred_at"], null),
|
|
579
|
+
significance: pick(item["significance"], item.confidence, 0),
|
|
580
|
+
participants: item["participants"] || [],
|
|
581
|
+
metadata: obj(item.metadata),
|
|
582
|
+
...normalizeTemporalFields(item)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function normalizeEmotion(item = {}) {
|
|
586
|
+
return {
|
|
587
|
+
id: item.id || "",
|
|
588
|
+
emotionType: pick(item["emotionType"], item["emotion_type"], ""),
|
|
589
|
+
intensity: pick(item["intensity"], item.confidence, 0),
|
|
590
|
+
detectedAt: pick(item["detectedAt"], item["detected_at"], null),
|
|
591
|
+
context: item["context"] || "",
|
|
592
|
+
metadata: obj(item.metadata),
|
|
593
|
+
...normalizeTemporalFields(item)
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function normalizeTemporalEvent(item = {}) {
|
|
597
|
+
return {
|
|
598
|
+
id: item.id || "",
|
|
599
|
+
content: item.content || "",
|
|
600
|
+
eventDate: pick(item["eventDate"], item.event_date, null),
|
|
601
|
+
validUntil: pick(item["validUntil"], item.valid_until, null),
|
|
602
|
+
temporalCategory: pick(item["temporalCategory"], item.temporal_category, ""),
|
|
603
|
+
temporalConfidence: pick(item["temporalConfidence"], item.temporal_confidence, 0),
|
|
604
|
+
confidence: pick(item.confidence, 0),
|
|
605
|
+
source: item.source || "",
|
|
606
|
+
extractedAt: pick(item["extractedAt"], item.extracted_at, null),
|
|
607
|
+
metadata: obj(item.metadata)
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function normalizeContextMetadata(metadata = {}) {
|
|
611
|
+
return {
|
|
612
|
+
correlationId: pick(metadata["correlationId"], metadata["correlation_id"], ""),
|
|
613
|
+
ttlSeconds: pick(metadata["ttlSeconds"], metadata["ttl_seconds"], 0),
|
|
614
|
+
source: metadata["source"] || "unknown",
|
|
615
|
+
retrievedAt: pick(metadata["retrievedAt"], metadata["retrieved_at"], null),
|
|
616
|
+
compactionApplied: pick(
|
|
617
|
+
metadata["compactionApplied"],
|
|
618
|
+
metadata["compaction_applied"],
|
|
619
|
+
null
|
|
620
|
+
)
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function normalizeConversationContext(value) {
|
|
624
|
+
if (!value) return null;
|
|
625
|
+
return {
|
|
626
|
+
summary: value.summary || null,
|
|
627
|
+
currentState: pick(value["currentState"], value.current_state, {}),
|
|
628
|
+
keyExtractions: pick(value["keyExtractions"], value.key_extractions, {}),
|
|
629
|
+
recentTurns: pick(value["recentTurns"], value.recent_turns, []),
|
|
630
|
+
compactionId: pick(value["compactionId"], value.compaction_id, null),
|
|
631
|
+
compactedAt: pick(value["compactedAt"], value.compacted_at, null),
|
|
632
|
+
conversationId: pick(value["conversationId"], value.conversation_id, null)
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function normalizeContextResponse(result = {}) {
|
|
636
|
+
const context = result["context"] ?? result;
|
|
637
|
+
return {
|
|
638
|
+
facts: (context.facts || []).map((i) => normalizeFact(i)),
|
|
639
|
+
preferences: (context.preferences || []).map((i) => normalizePreference(i)),
|
|
640
|
+
episodes: (context.episodes || []).map((i) => normalizeEpisode(i)),
|
|
641
|
+
emotions: (context.emotions || []).map((i) => normalizeEmotion(i)),
|
|
642
|
+
temporalEvents: (pick(context["temporalEvents"], context.temporal_events, []) || []).map((i) => normalizeTemporalEvent(i)),
|
|
643
|
+
conversationContext: normalizeConversationContext(
|
|
644
|
+
pick(
|
|
645
|
+
context["conversationContext"],
|
|
646
|
+
context.conversation_context,
|
|
647
|
+
null
|
|
648
|
+
)
|
|
649
|
+
),
|
|
650
|
+
metadata: normalizeContextMetadata(obj(context.metadata)),
|
|
651
|
+
rawResponse: pick(context["rawResponse"], context["raw_response"], {})
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/context/flatten.ts
|
|
656
|
+
var s = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
|
|
657
|
+
var n = (v, d = 0) => typeof v === "number" ? v : d;
|
|
658
|
+
var o = (v) => v ?? {};
|
|
659
|
+
var dt = (v) => v ? String(v) : null;
|
|
660
|
+
function temporal(item) {
|
|
661
|
+
return {
|
|
662
|
+
event_date: dt(item["event_date"]),
|
|
663
|
+
valid_until: dt(item["valid_until"]),
|
|
664
|
+
temporal_category: item["temporal_category"] ?? null,
|
|
665
|
+
temporal_confidence: n(item["temporal_confidence"], 0)
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function flattenContextItems(context) {
|
|
669
|
+
const items = [];
|
|
670
|
+
for (const f of context.facts ?? []) {
|
|
671
|
+
items.push({
|
|
672
|
+
id: s(f["id"]),
|
|
673
|
+
memory: s(f["content"]),
|
|
674
|
+
score: n(f["confidence"]),
|
|
675
|
+
source: s(f["source"]),
|
|
676
|
+
metadata: o(f["metadata"]),
|
|
677
|
+
context_type: "fact",
|
|
678
|
+
...temporal(f)
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
for (const p of context.preferences ?? []) {
|
|
682
|
+
items.push({
|
|
683
|
+
id: s(p["id"]),
|
|
684
|
+
memory: s(p["content"]),
|
|
685
|
+
score: n(p["strength"]),
|
|
686
|
+
source: s(p["source"]),
|
|
687
|
+
metadata: o(p["metadata"]),
|
|
688
|
+
context_type: "preference",
|
|
689
|
+
...temporal(p)
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
for (const e of context.episodes ?? []) {
|
|
693
|
+
items.push({
|
|
694
|
+
id: s(e["id"]),
|
|
695
|
+
memory: s(e["summary"]),
|
|
696
|
+
score: n(e["significance"]),
|
|
697
|
+
metadata: o(e["metadata"]),
|
|
698
|
+
context_type: "episode",
|
|
699
|
+
...temporal(e)
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
for (const m of context.emotions ?? []) {
|
|
703
|
+
items.push({
|
|
704
|
+
id: s(m["id"]),
|
|
705
|
+
memory: s(m["context"]),
|
|
706
|
+
score: n(m["intensity"]),
|
|
707
|
+
metadata: o(m["metadata"]),
|
|
708
|
+
context_type: "emotion",
|
|
709
|
+
...temporal(m)
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
for (const t of context.temporal_events ?? []) {
|
|
713
|
+
items.push({
|
|
714
|
+
id: s(t["id"]),
|
|
715
|
+
memory: s(t["content"]),
|
|
716
|
+
score: n(t["temporal_confidence"]),
|
|
717
|
+
source: s(t["source"]),
|
|
718
|
+
metadata: o(t["metadata"]),
|
|
719
|
+
context_type: "temporal_event",
|
|
720
|
+
...temporal(t)
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return items;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/memories/interface.ts
|
|
727
|
+
var DOCUMENT_TYPES = [
|
|
728
|
+
"ai-chat-conversation",
|
|
729
|
+
"document",
|
|
730
|
+
"email",
|
|
731
|
+
"pdf",
|
|
732
|
+
"image",
|
|
733
|
+
"audio",
|
|
734
|
+
"meeting-transcript"
|
|
735
|
+
];
|
|
736
|
+
var INGEST_MODES = ["fast", "long-range"];
|
|
737
|
+
var DEFAULT_DOCUMENT_TYPE = "ai-chat-conversation";
|
|
738
|
+
var DEFAULT_INGEST_MODE = "long-range";
|
|
739
|
+
function either(o2, snake, camel) {
|
|
740
|
+
const a = o2[snake];
|
|
741
|
+
if (a !== void 0) return a;
|
|
742
|
+
const b = o2[camel];
|
|
743
|
+
return b === void 0 ? void 0 : b;
|
|
744
|
+
}
|
|
745
|
+
function isoOrNull(v) {
|
|
746
|
+
if (v === void 0 || v === null) return null;
|
|
747
|
+
return v instanceof Date ? v.toISOString() : v;
|
|
748
|
+
}
|
|
749
|
+
function buildCreateBody(options) {
|
|
750
|
+
if (!options.document) throw new InvalidInputError("document is required");
|
|
751
|
+
const o2 = options;
|
|
752
|
+
const documentType = either(o2, "document_type", "documentType") ?? DEFAULT_DOCUMENT_TYPE;
|
|
753
|
+
const mode = options.mode ?? DEFAULT_INGEST_MODE;
|
|
754
|
+
if (!DOCUMENT_TYPES.includes(documentType)) {
|
|
755
|
+
throw new InvalidInputError(
|
|
756
|
+
`Invalid document_type '${documentType}'. Expected one of: ${DOCUMENT_TYPES.join(", ")}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
if (!INGEST_MODES.includes(mode)) {
|
|
760
|
+
throw new InvalidInputError(
|
|
761
|
+
`Invalid mode '${mode}'. Expected one of: ${INGEST_MODES.join(", ")}`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
document: options.document,
|
|
766
|
+
document_type: documentType,
|
|
767
|
+
document_id: either(o2, "document_id", "documentId") ?? null,
|
|
768
|
+
document_created_at: isoOrNull(
|
|
769
|
+
either(o2, "document_created_at", "documentCreatedAt")
|
|
770
|
+
),
|
|
771
|
+
user_id: either(o2, "user_id", "userId") ?? null,
|
|
772
|
+
customer_id: either(o2, "customer_id", "customerId") ?? null,
|
|
773
|
+
mode,
|
|
774
|
+
metadata: options.metadata ?? {}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "error", "cancelled"]);
|
|
778
|
+
function createMemoriesInterface(transport) {
|
|
779
|
+
return {
|
|
780
|
+
async create(options) {
|
|
781
|
+
return transport.request("memories_create", { body: buildCreateBody(options) });
|
|
782
|
+
},
|
|
783
|
+
async batch_create(options) {
|
|
784
|
+
const docs = options.documents;
|
|
785
|
+
if (!Array.isArray(docs) || docs.length === 0) {
|
|
786
|
+
throw new InvalidInputError("documents must be a non-empty array");
|
|
787
|
+
}
|
|
788
|
+
const failFast = options.fail_fast ?? options.failFast ?? false;
|
|
789
|
+
return transport.request("memories_batch", {
|
|
790
|
+
body: { documents: docs.map(buildCreateBody), fail_fast: failFast }
|
|
791
|
+
});
|
|
792
|
+
},
|
|
793
|
+
async create_from_file(options) {
|
|
794
|
+
const userId = options.user_id ?? options.userId;
|
|
795
|
+
const customerId = options.customer_id ?? options.customerId;
|
|
796
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
797
|
+
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
798
|
+
const filePath = options.file_path ?? options.filePath;
|
|
799
|
+
const sources = [filePath, options.file, options.text].filter((v) => v !== void 0);
|
|
800
|
+
if (sources.length === 0) {
|
|
801
|
+
throw new InvalidInputError("One of file_path, file, or text must be provided.");
|
|
802
|
+
}
|
|
803
|
+
if (sources.length > 1) {
|
|
804
|
+
throw new InvalidInputError(
|
|
805
|
+
"Provide exactly one of file_path, file, or text, not several."
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const form = new FormData();
|
|
809
|
+
form.set("user_id", userId);
|
|
810
|
+
form.set("customer_id", customerId);
|
|
811
|
+
form.set("relationship_type", options.relationship_type ?? options.relationshipType ?? "b2c");
|
|
812
|
+
form.set("mode", String(options.mode ?? DEFAULT_INGEST_MODE));
|
|
813
|
+
const documentType = options.document_type ?? options.documentType;
|
|
814
|
+
if (documentType) form.set("document_type", String(documentType));
|
|
815
|
+
if (options.metadata !== void 0 && Object.keys(options.metadata).length > 0) {
|
|
816
|
+
form.set("metadata", JSON.stringify(options.metadata));
|
|
817
|
+
}
|
|
818
|
+
if (filePath !== void 0) {
|
|
819
|
+
const { readFile } = await import("fs/promises");
|
|
820
|
+
const { basename } = await import("path");
|
|
821
|
+
const bytes = await readFile(filePath);
|
|
822
|
+
form.set("file", new Blob([bytes]), basename(filePath));
|
|
823
|
+
} else if (options.file !== void 0) {
|
|
824
|
+
const blob = options.file instanceof Blob ? options.file : new Blob([options.file]);
|
|
825
|
+
form.set("file", blob, options.filename ?? "upload");
|
|
826
|
+
} else {
|
|
827
|
+
form.set("text", String(options.text));
|
|
828
|
+
}
|
|
829
|
+
return transport.request("memories_upload", {
|
|
830
|
+
body: form,
|
|
831
|
+
rawBody: true
|
|
832
|
+
});
|
|
833
|
+
},
|
|
834
|
+
async get(memoryId) {
|
|
835
|
+
if (!memoryId) throw new InvalidInputError("memory_id is required");
|
|
836
|
+
return transport.request("memories_get", { pathParams: { memory_id: memoryId } });
|
|
837
|
+
},
|
|
838
|
+
async update(options) {
|
|
839
|
+
const memoryId = options.memory_id ?? options.memoryId;
|
|
840
|
+
if (!memoryId) throw new InvalidInputError("memory_id is required");
|
|
841
|
+
if (!options.document) throw new InvalidInputError("document is required");
|
|
842
|
+
const body = {
|
|
843
|
+
document: options.document,
|
|
844
|
+
merge_strategy: options.merge_strategy ?? options.mergeStrategy ?? "smart-merge"
|
|
845
|
+
};
|
|
846
|
+
const dt2 = options.document_type ?? options.documentType;
|
|
847
|
+
if (dt2 !== void 0) body["document_type"] = dt2;
|
|
848
|
+
if (options.metadata !== void 0) body["metadata"] = options.metadata;
|
|
849
|
+
return transport.request("memories_update", {
|
|
850
|
+
pathParams: { memory_id: memoryId },
|
|
851
|
+
body
|
|
852
|
+
});
|
|
853
|
+
},
|
|
854
|
+
async delete(memoryId) {
|
|
855
|
+
if (!memoryId) {
|
|
856
|
+
throw new InvalidInputError(
|
|
857
|
+
"memory_id is required. Deleting by user is no longer supported: the previous implementation tracked ids in process memory, so it silently deleted nothing in any serverless or multi-process deployment while reporting success."
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
return transport.request("memories_delete", { pathParams: { memory_id: memoryId } });
|
|
861
|
+
},
|
|
862
|
+
async status(ingestionId) {
|
|
863
|
+
if (!ingestionId) throw new InvalidInputError("ingestion_id is required");
|
|
864
|
+
return transport.request("memories_status", {
|
|
865
|
+
pathParams: { ingestion_id: ingestionId }
|
|
866
|
+
});
|
|
867
|
+
},
|
|
868
|
+
async wait_for_completion(ingestionId, options = {}) {
|
|
869
|
+
if (!ingestionId) throw new InvalidInputError("ingestion_id is required");
|
|
870
|
+
const timeoutMs = (options.timeout_seconds ?? options.timeoutSeconds ?? 300) * 1e3;
|
|
871
|
+
const pollMs = (options.poll_interval_seconds ?? options.pollIntervalSeconds ?? 2) * 1e3;
|
|
872
|
+
const deadline = Date.now() + timeoutMs;
|
|
873
|
+
let last = {};
|
|
874
|
+
for (; ; ) {
|
|
875
|
+
last = await this.status(ingestionId);
|
|
876
|
+
const status = String(last["status"] ?? "").toLowerCase();
|
|
877
|
+
if (TERMINAL_STATUSES.has(status)) return last;
|
|
878
|
+
if (Date.now() >= deadline) {
|
|
879
|
+
return last;
|
|
880
|
+
}
|
|
881
|
+
await sleep2(Math.min(pollMs, Math.max(0, deadline - Date.now())), options.signal);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function sleep2(ms, signal) {
|
|
887
|
+
if (ms <= 0) return Promise.resolve();
|
|
888
|
+
return new Promise((resolve, reject) => {
|
|
889
|
+
const timer = setTimeout(() => {
|
|
890
|
+
signal?.removeEventListener("abort", onAbort);
|
|
891
|
+
resolve();
|
|
892
|
+
}, ms);
|
|
893
|
+
function onAbort() {
|
|
894
|
+
clearTimeout(timer);
|
|
895
|
+
reject(signal?.reason ?? new Error("Aborted"));
|
|
896
|
+
}
|
|
897
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/util/validators.ts
|
|
902
|
+
var INSTANCE_ID_RE = /^inst_[0-9a-fA-F]{16}$/;
|
|
903
|
+
var UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
904
|
+
function validateConversationId(conversationId) {
|
|
905
|
+
if (conversationId === void 0 || conversationId === null || conversationId === "") return;
|
|
906
|
+
if (typeof conversationId !== "string" || !UUID_RE.test(conversationId)) {
|
|
907
|
+
throw new InvalidConversationIdError(`Invalid conversation ID: ${String(conversationId)}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function validateInstanceId(instanceId) {
|
|
911
|
+
if (instanceId === void 0 || instanceId === null || instanceId === "") return;
|
|
912
|
+
if (typeof instanceId !== "string" || !INSTANCE_ID_RE.test(instanceId)) {
|
|
913
|
+
throw new InvalidInstanceIdError(`Invalid instance ID: ${String(instanceId)}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/conversation/interface.ts
|
|
918
|
+
function either2(o2, snake, camel) {
|
|
919
|
+
const a = o2[snake];
|
|
920
|
+
if (a !== void 0) return a;
|
|
921
|
+
const b = o2[camel];
|
|
922
|
+
return b === void 0 ? void 0 : b;
|
|
923
|
+
}
|
|
924
|
+
function iso(v) {
|
|
925
|
+
if (v === void 0) return void 0;
|
|
926
|
+
return v instanceof Date ? v.toISOString() : v;
|
|
927
|
+
}
|
|
928
|
+
function buildRecordMessageBody(options) {
|
|
929
|
+
const o2 = options;
|
|
930
|
+
const conversationId = either2(o2, "conversation_id", "conversationId");
|
|
931
|
+
const userId = either2(o2, "user_id", "userId");
|
|
932
|
+
const customerId = either2(o2, "customer_id", "customerId");
|
|
933
|
+
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
934
|
+
validateConversationId(conversationId);
|
|
935
|
+
if (!options.role) throw new InvalidInputError("role is required");
|
|
936
|
+
if (options.content === void 0 || options.content === null) {
|
|
937
|
+
throw new InvalidInputError("content is required");
|
|
938
|
+
}
|
|
939
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
940
|
+
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
941
|
+
const body = {
|
|
942
|
+
conversation_id: conversationId,
|
|
943
|
+
role: options.role,
|
|
944
|
+
content: options.content,
|
|
945
|
+
user_id: userId,
|
|
946
|
+
customer_id: customerId,
|
|
947
|
+
// Python always sends this, defaulting to {}.
|
|
948
|
+
metadata: options.metadata ?? {}
|
|
949
|
+
};
|
|
950
|
+
const sessionId = either2(o2, "session_id", "sessionId");
|
|
951
|
+
if (sessionId !== void 0) body["session_id"] = sessionId;
|
|
952
|
+
return body;
|
|
953
|
+
}
|
|
954
|
+
function buildIngestTranscriptBody(options) {
|
|
955
|
+
const o2 = options;
|
|
956
|
+
const conversationId = either2(o2, "conversation_id", "conversationId");
|
|
957
|
+
const userId = either2(o2, "user_id", "userId");
|
|
958
|
+
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
959
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
960
|
+
if (options.transcript === void 0 || options.transcript === null) {
|
|
961
|
+
throw new InvalidInputError("transcript is required");
|
|
962
|
+
}
|
|
963
|
+
const body = {
|
|
964
|
+
conversation_id: conversationId,
|
|
965
|
+
user_id: userId,
|
|
966
|
+
transcript: options.transcript
|
|
967
|
+
};
|
|
968
|
+
const customerId = either2(o2, "customer_id", "customerId");
|
|
969
|
+
const conversationType = either2(o2, "conversation_type", "conversationType");
|
|
970
|
+
const startedAt = iso(either2(o2, "started_at", "startedAt"));
|
|
971
|
+
const endedAt = iso(either2(o2, "ended_at", "endedAt"));
|
|
972
|
+
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
973
|
+
if (conversationType !== void 0) body["conversation_type"] = conversationType;
|
|
974
|
+
if (options.analysis !== void 0) body["analysis"] = options.analysis;
|
|
975
|
+
if (options.metadata !== void 0) body["metadata"] = options.metadata;
|
|
976
|
+
if (startedAt !== void 0) body["started_at"] = startedAt;
|
|
977
|
+
if (endedAt !== void 0) body["ended_at"] = endedAt;
|
|
978
|
+
return body;
|
|
979
|
+
}
|
|
980
|
+
function buildCompactBody(options) {
|
|
981
|
+
const o2 = options;
|
|
982
|
+
const conversationId = either2(o2, "conversation_id", "conversationId");
|
|
983
|
+
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
984
|
+
validateConversationId(conversationId);
|
|
985
|
+
const body = { conversation_id: conversationId, force: options.force ?? false };
|
|
986
|
+
const strategy = options.strategy ?? either2(o2, "compaction_level", "compactionLevel");
|
|
987
|
+
const targetTokens = either2(o2, "target_tokens", "targetTokens");
|
|
988
|
+
if (strategy !== void 0) body["strategy"] = strategy;
|
|
989
|
+
if (targetTokens !== void 0) body["target_tokens"] = targetTokens;
|
|
990
|
+
return body;
|
|
991
|
+
}
|
|
992
|
+
var CompactionSubscribers = class {
|
|
993
|
+
#byConversation = /* @__PURE__ */ new Map();
|
|
994
|
+
add(conversationId, callback) {
|
|
995
|
+
const list = this.#byConversation.get(conversationId);
|
|
996
|
+
if (list === void 0) this.#byConversation.set(conversationId, [callback]);
|
|
997
|
+
else list.push(callback);
|
|
998
|
+
let unsubscribed = false;
|
|
999
|
+
return () => {
|
|
1000
|
+
if (unsubscribed) return;
|
|
1001
|
+
unsubscribed = true;
|
|
1002
|
+
const current = this.#byConversation.get(conversationId);
|
|
1003
|
+
if (current === void 0) return;
|
|
1004
|
+
const at = current.indexOf(callback);
|
|
1005
|
+
if (at !== -1) current.splice(at, 1);
|
|
1006
|
+
if (current.length === 0) this.#byConversation.delete(conversationId);
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
removeAll(conversationId) {
|
|
1010
|
+
if (conversationId === void 0) {
|
|
1011
|
+
let count = 0;
|
|
1012
|
+
for (const list2 of this.#byConversation.values()) count += list2.length;
|
|
1013
|
+
this.#byConversation.clear();
|
|
1014
|
+
return count;
|
|
1015
|
+
}
|
|
1016
|
+
const list = this.#byConversation.get(conversationId);
|
|
1017
|
+
if (list === void 0) return 0;
|
|
1018
|
+
this.#byConversation.delete(conversationId);
|
|
1019
|
+
return list.length;
|
|
1020
|
+
}
|
|
1021
|
+
/** Fires subscribers in registration order. A throwing listener must not break the stream reader. */
|
|
1022
|
+
dispatch(conversationId, bundle, onError) {
|
|
1023
|
+
for (const callback of [...this.#byConversation.get(conversationId) ?? []]) {
|
|
1024
|
+
try {
|
|
1025
|
+
const result = callback(bundle);
|
|
1026
|
+
if (result instanceof Promise) result.catch(onError);
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
onError(error);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
|
|
1034
|
+
}) {
|
|
1035
|
+
const convId = (v) => {
|
|
1036
|
+
const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
|
|
1037
|
+
if (!id) throw new InvalidInputError("conversation_id is required");
|
|
1038
|
+
validateConversationId(id);
|
|
1039
|
+
return id;
|
|
1040
|
+
};
|
|
1041
|
+
return {
|
|
1042
|
+
async record_message(options) {
|
|
1043
|
+
const body = buildRecordMessageBody(options);
|
|
1044
|
+
const result = await transport.request("conversations_messages", { body });
|
|
1045
|
+
onWrite({
|
|
1046
|
+
userId: body["user_id"],
|
|
1047
|
+
customerId: body["customer_id"]
|
|
1048
|
+
});
|
|
1049
|
+
return result;
|
|
1050
|
+
},
|
|
1051
|
+
async record_messages_batch(messages) {
|
|
1052
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1053
|
+
throw new InvalidInputError("messages must be a non-empty array");
|
|
1054
|
+
}
|
|
1055
|
+
const bodies = messages.map(buildRecordMessageBody);
|
|
1056
|
+
const result = await transport.request("conversations_messages_batch", {
|
|
1057
|
+
body: { messages: bodies }
|
|
1058
|
+
});
|
|
1059
|
+
for (const body of bodies) {
|
|
1060
|
+
onWrite({
|
|
1061
|
+
userId: body["user_id"],
|
|
1062
|
+
customerId: body["customer_id"]
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
return result;
|
|
1066
|
+
},
|
|
1067
|
+
async ingest_transcript(options) {
|
|
1068
|
+
const body = buildIngestTranscriptBody(options);
|
|
1069
|
+
const result = await transport.request("conversations_ingest", { body });
|
|
1070
|
+
onWrite({
|
|
1071
|
+
userId: body["user_id"],
|
|
1072
|
+
customerId: body["customer_id"]
|
|
1073
|
+
});
|
|
1074
|
+
return result;
|
|
1075
|
+
},
|
|
1076
|
+
context: {
|
|
1077
|
+
async fetch(options = {}) {
|
|
1078
|
+
validateConversationId(
|
|
1079
|
+
options.conversation_id ?? options.conversationId
|
|
1080
|
+
);
|
|
1081
|
+
return fetchContext(transport, "conversation", options);
|
|
1082
|
+
},
|
|
1083
|
+
get_context_for_prompt: async (options = {}) => {
|
|
1084
|
+
const id = convId(options);
|
|
1085
|
+
const result = await transport.request("context_for_prompt", {
|
|
1086
|
+
pathParams: { conversation_id: id },
|
|
1087
|
+
query: { style: options.style ?? "structured" }
|
|
1088
|
+
});
|
|
1089
|
+
return result?.["context_for_prompt"] ?? result ?? {};
|
|
1090
|
+
},
|
|
1091
|
+
async compact(options) {
|
|
1092
|
+
return transport.request("conversations_compact", { body: buildCompactBody(options) });
|
|
1093
|
+
},
|
|
1094
|
+
get_compacted: async (options) => {
|
|
1095
|
+
const id = convId(options);
|
|
1096
|
+
const query = { format: options.format ?? "structured" };
|
|
1097
|
+
if (options.version !== void 0) query["version"] = options.version;
|
|
1098
|
+
try {
|
|
1099
|
+
return await transport.request("conversations_compacted", {
|
|
1100
|
+
pathParams: { conversation_id: id },
|
|
1101
|
+
query
|
|
1102
|
+
});
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
if (error.code === "context_not_found") return null;
|
|
1105
|
+
throw error;
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
async get_compaction_status(options) {
|
|
1109
|
+
return transport.request("conversations_compaction_status", {
|
|
1110
|
+
pathParams: { conversation_id: convId(options) }
|
|
1111
|
+
});
|
|
1112
|
+
},
|
|
1113
|
+
/**
|
|
1114
|
+
* Fire `callback` whenever a `compaction_update` bundle arrives on the
|
|
1115
|
+
* Listen stream for this conversation.
|
|
1116
|
+
*
|
|
1117
|
+
* Requires an active `client.instance.listen()`; without it no bundles
|
|
1118
|
+
* arrive and the callback never fires. Registering before listening is
|
|
1119
|
+
* fine, which is why this does not throw when the stream is down.
|
|
1120
|
+
*
|
|
1121
|
+
* Returns an idempotent unsubscribe thunk, matching Python.
|
|
1122
|
+
*/
|
|
1123
|
+
subscribe_to_compaction_updates(conversationId, callback) {
|
|
1124
|
+
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
1125
|
+
if (typeof callback !== "function") throw new InvalidInputError("callback is required");
|
|
1126
|
+
return subscribers.add(conversationId, callback);
|
|
1127
|
+
},
|
|
1128
|
+
/** Remove subscribers for one conversation, or all of them. Returns the count removed. */
|
|
1129
|
+
unsubscribe_all_compaction_updates(conversationId) {
|
|
1130
|
+
return subscribers.removeAll(conversationId);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// src/user/interface.ts
|
|
1137
|
+
function createUserNamespace(transport) {
|
|
1138
|
+
return {
|
|
1139
|
+
async get_profile(options) {
|
|
1140
|
+
const userId = typeof options === "string" ? options : options.user_id ?? options.userId;
|
|
1141
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
1142
|
+
const customerId = typeof options === "string" ? void 0 : options.customer_id ?? options.customerId;
|
|
1143
|
+
return transport.request("user_profile", {
|
|
1144
|
+
pathParams: { user_id: userId },
|
|
1145
|
+
// Sent only when provided, matching Python. Note the server returns
|
|
1146
|
+
// 500 rather than a clean error if it needs one and does not get it.
|
|
1147
|
+
...customerId !== void 0 ? { query: { customer_id: customerId } } : {}
|
|
1148
|
+
});
|
|
1149
|
+
},
|
|
1150
|
+
context: { async fetch(options = {}) {
|
|
1151
|
+
return fetchContext(transport, "user", options);
|
|
1152
|
+
} }
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// src/credits/interface.ts
|
|
1157
|
+
function createCreditsNamespace(transport) {
|
|
1158
|
+
return {
|
|
1159
|
+
async get_balance() {
|
|
1160
|
+
return transport.request("credits_balance");
|
|
1161
|
+
},
|
|
1162
|
+
async get_ledger(options = {}) {
|
|
1163
|
+
return transport.request("credits_ledger", {
|
|
1164
|
+
query: { limit: options.limit ?? 50, offset: options.offset ?? 0 }
|
|
1165
|
+
});
|
|
1166
|
+
},
|
|
1167
|
+
async estimate(options) {
|
|
1168
|
+
return transport.request("credits_estimate", {
|
|
1169
|
+
body: {
|
|
1170
|
+
metric_type: options.metric_type ?? options.metricType ?? null,
|
|
1171
|
+
units: options.units ?? null,
|
|
1172
|
+
item_count: options.item_count ?? options.itemCount ?? null,
|
|
1173
|
+
endpoint: options.endpoint ?? null,
|
|
1174
|
+
mode: options.mode ?? null
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
},
|
|
1178
|
+
async redeem(code) {
|
|
1179
|
+
if (!code) throw new InvalidInputError("code is required");
|
|
1180
|
+
return transport.request("credits_redeem", { body: { code } });
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/cache/interface.ts
|
|
1186
|
+
function createCacheNamespace(cache) {
|
|
1187
|
+
return {
|
|
1188
|
+
clear: () => cache.clear(),
|
|
1189
|
+
clear_user: (userId) => {
|
|
1190
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
1191
|
+
cache.dropEntity(userId);
|
|
1192
|
+
},
|
|
1193
|
+
clear_customer: (customerId) => {
|
|
1194
|
+
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
1195
|
+
cache.dropEntity(customerId);
|
|
1196
|
+
},
|
|
1197
|
+
stats: () => ({ bundles: cache.size, items: cache.itemCount })
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/instance/interface.ts
|
|
1202
|
+
var DEFAULT_GRPC_HOST = "synap-cloud-prod.maximem.ai";
|
|
1203
|
+
var DEFAULT_GRPC_PORT = 443;
|
|
1204
|
+
function envPort() {
|
|
1205
|
+
const raw = getEnv("SYNAP_GRPC_PORT");
|
|
1206
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
1207
|
+
const parsed = Number(raw);
|
|
1208
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1209
|
+
}
|
|
1210
|
+
function envTls() {
|
|
1211
|
+
const raw = getEnv("SYNAP_GRPC_USE_TLS");
|
|
1212
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
1213
|
+
return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
|
|
1214
|
+
}
|
|
1215
|
+
function createInstanceNamespace(credentials, cache) {
|
|
1216
|
+
let client = null;
|
|
1217
|
+
let dispatchCompaction = null;
|
|
1218
|
+
const internals = {
|
|
1219
|
+
onCompactionUpdate(conversationId, bundle) {
|
|
1220
|
+
dispatchCompaction?.(conversationId, bundle);
|
|
1221
|
+
},
|
|
1222
|
+
setCompactionDispatcher(dispatch) {
|
|
1223
|
+
dispatchCompaction = dispatch;
|
|
1224
|
+
},
|
|
1225
|
+
isListening() {
|
|
1226
|
+
return client !== null && client.isConnected;
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
const namespace = {
|
|
1230
|
+
async listen(options = {}) {
|
|
1231
|
+
if (client !== null) {
|
|
1232
|
+
throw new ListeningAlreadyActiveError("Listening is already active on this instance");
|
|
1233
|
+
}
|
|
1234
|
+
const { GrpcStreamClient } = await import("./stream-client-ME5N5NK4.js");
|
|
1235
|
+
const onContext = options.on_context ?? options.onContext;
|
|
1236
|
+
const onDisconnect = options.on_disconnect ?? options.onDisconnect;
|
|
1237
|
+
const onReconnect = options.on_reconnect ?? options.onReconnect;
|
|
1238
|
+
let reconnectAttempts = 0;
|
|
1239
|
+
const created = new GrpcStreamClient(credentials(), cache, {
|
|
1240
|
+
host: options.host ?? getEnv("SYNAP_GRPC_HOST") ?? DEFAULT_GRPC_HOST,
|
|
1241
|
+
port: options.port ?? envPort() ?? DEFAULT_GRPC_PORT,
|
|
1242
|
+
useTls: options.use_tls ?? options.useTls ?? envTls() ?? true,
|
|
1243
|
+
...onContext !== void 0 ? { onContext } : {},
|
|
1244
|
+
onCompactionUpdate: (conversationId, bundle) => {
|
|
1245
|
+
internals.onCompactionUpdate(conversationId, bundle);
|
|
1246
|
+
},
|
|
1247
|
+
onStateChange: (state) => {
|
|
1248
|
+
if (state === "reconnecting") {
|
|
1249
|
+
reconnectAttempts += 1;
|
|
1250
|
+
onReconnect?.(reconnectAttempts);
|
|
1251
|
+
} else if (state === "disconnected") {
|
|
1252
|
+
onDisconnect?.("stream disconnected");
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
try {
|
|
1257
|
+
await created.connect();
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
if (error instanceof SynapError) throw error;
|
|
1260
|
+
throw new SynapError(
|
|
1261
|
+
`Could not open the Synap anticipation stream: ${error?.message ?? String(error)}`,
|
|
1262
|
+
{ cause: error }
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
client = created;
|
|
1266
|
+
},
|
|
1267
|
+
async stop_listening() {
|
|
1268
|
+
if (client === null) return;
|
|
1269
|
+
const active = client;
|
|
1270
|
+
client = null;
|
|
1271
|
+
await active.disconnect();
|
|
1272
|
+
},
|
|
1273
|
+
async send_message(options) {
|
|
1274
|
+
if (client === null) throw new ListeningNotActiveError("Listening is not active");
|
|
1275
|
+
if (options.content === void 0) {
|
|
1276
|
+
throw new ListeningNotActiveError("content is required");
|
|
1277
|
+
}
|
|
1278
|
+
client.sendConversationEvent({
|
|
1279
|
+
event_type: options.event_type ?? options.eventType ?? "user_message",
|
|
1280
|
+
content: options.content,
|
|
1281
|
+
role: options.role ?? "user",
|
|
1282
|
+
conversation_id: options.conversation_id ?? options.conversationId ?? "",
|
|
1283
|
+
user_id: options.user_id ?? options.userId ?? "",
|
|
1284
|
+
customer_id: options.customer_id ?? options.customerId ?? "",
|
|
1285
|
+
session_id: options.session_id ?? options.sessionId ?? "",
|
|
1286
|
+
metadata: options.metadata ?? {},
|
|
1287
|
+
timestamp_ms: Date.now(),
|
|
1288
|
+
search_queries: options.search_queries ?? options.searchQueries ?? [],
|
|
1289
|
+
context_types: options.context_types ?? options.contextTypes ?? []
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
async record_thinking(options) {
|
|
1293
|
+
if (client === null) throw new ListeningNotActiveError("Listening is not active");
|
|
1294
|
+
client.sendConversationEvent({
|
|
1295
|
+
// Python emits reasoning as an `agent_thinking` conversation event.
|
|
1296
|
+
event_type: "agent_thinking",
|
|
1297
|
+
content: options.content,
|
|
1298
|
+
role: "assistant",
|
|
1299
|
+
conversation_id: options.conversation_id ?? options.conversationId ?? "",
|
|
1300
|
+
user_id: options.user_id ?? options.userId ?? "",
|
|
1301
|
+
customer_id: options.customer_id ?? options.customerId ?? "",
|
|
1302
|
+
session_id: options.session_id ?? options.sessionId ?? "",
|
|
1303
|
+
metadata: options.metadata ?? {},
|
|
1304
|
+
timestamp_ms: Date.now()
|
|
1305
|
+
});
|
|
1306
|
+
},
|
|
1307
|
+
get is_listening() {
|
|
1308
|
+
return client !== null && client.isConnected;
|
|
1309
|
+
},
|
|
1310
|
+
_internal: internals
|
|
1311
|
+
};
|
|
1312
|
+
return namespace;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// src/version.ts
|
|
1316
|
+
var SDK_VERSION = "1.0.0-beta.0";
|
|
1317
|
+
|
|
1318
|
+
// src/context/unified.ts
|
|
1319
|
+
var ITEM_COLLECTIONS = [
|
|
1320
|
+
"facts",
|
|
1321
|
+
"preferences",
|
|
1322
|
+
"episodes",
|
|
1323
|
+
"emotions",
|
|
1324
|
+
"temporal_events"
|
|
1325
|
+
];
|
|
1326
|
+
function itemId(item) {
|
|
1327
|
+
const id = item.id ?? item.item_id;
|
|
1328
|
+
return typeof id === "string" && id !== "" ? id : void 0;
|
|
1329
|
+
}
|
|
1330
|
+
function mergeScopeResults(scopeResults) {
|
|
1331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1332
|
+
const out = {
|
|
1333
|
+
facts: [],
|
|
1334
|
+
preferences: [],
|
|
1335
|
+
episodes: [],
|
|
1336
|
+
emotions: [],
|
|
1337
|
+
temporal_events: [],
|
|
1338
|
+
scope_map: {},
|
|
1339
|
+
conversation_context: null,
|
|
1340
|
+
profile: null,
|
|
1341
|
+
conversations: null,
|
|
1342
|
+
formatted_context: "",
|
|
1343
|
+
scopes_queried: [],
|
|
1344
|
+
total_items: 0,
|
|
1345
|
+
metadata: null
|
|
1346
|
+
};
|
|
1347
|
+
for (const [scopeName, response] of scopeResults) {
|
|
1348
|
+
out.scopes_queried.push(scopeName);
|
|
1349
|
+
if (out.metadata === null && response.metadata !== void 0) {
|
|
1350
|
+
out.metadata = response.metadata;
|
|
1351
|
+
}
|
|
1352
|
+
if (out.profile === null && response["profile"] !== void 0 && response["profile"] !== null) {
|
|
1353
|
+
out.profile = response["profile"];
|
|
1354
|
+
}
|
|
1355
|
+
if (out.conversations === null && response["conversations"] !== void 0 && response["conversations"] !== null) {
|
|
1356
|
+
out.conversations = response["conversations"];
|
|
1357
|
+
}
|
|
1358
|
+
for (const collection of ITEM_COLLECTIONS) {
|
|
1359
|
+
const items = response[collection];
|
|
1360
|
+
if (!Array.isArray(items)) continue;
|
|
1361
|
+
for (const item of items) {
|
|
1362
|
+
const id = itemId(item);
|
|
1363
|
+
if (id !== void 0) {
|
|
1364
|
+
if (seen.has(id)) continue;
|
|
1365
|
+
seen.add(id);
|
|
1366
|
+
out.scope_map[id] = scopeName;
|
|
1367
|
+
}
|
|
1368
|
+
out[collection].push(item);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
out.total_items = ITEM_COLLECTIONS.reduce((n2, c) => n2 + out[c].length, 0);
|
|
1373
|
+
return out;
|
|
1374
|
+
}
|
|
1375
|
+
function oneDecimal(value) {
|
|
1376
|
+
return value.toFixed(1);
|
|
1377
|
+
}
|
|
1378
|
+
function isoDate(value) {
|
|
1379
|
+
if (typeof value !== "string" || value === "") return null;
|
|
1380
|
+
const parsed = new Date(value);
|
|
1381
|
+
if (Number.isNaN(parsed.getTime())) return null;
|
|
1382
|
+
return parsed.toISOString().slice(0, 10);
|
|
1383
|
+
}
|
|
1384
|
+
function numberOf(value) {
|
|
1385
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1386
|
+
}
|
|
1387
|
+
function scopeOf(ctx, item) {
|
|
1388
|
+
const id = itemId(item);
|
|
1389
|
+
return (id !== void 0 ? ctx.scope_map[id] : void 0) ?? "unknown";
|
|
1390
|
+
}
|
|
1391
|
+
function bulletWithExtras(text, extras) {
|
|
1392
|
+
return extras.length > 0 ? `- ${text} [${extras.join(", ")}]` : `- ${text}`;
|
|
1393
|
+
}
|
|
1394
|
+
function formatForPrompt(ctx, options = {}) {
|
|
1395
|
+
const includeScope = options.includeScope ?? false;
|
|
1396
|
+
const includeConversationContext = options.includeConversationContext ?? true;
|
|
1397
|
+
const sections = [];
|
|
1398
|
+
if (ctx.facts.length > 0) {
|
|
1399
|
+
const lines = ctx.facts.map((f) => {
|
|
1400
|
+
let line = `- ${String(f.content ?? "")}`;
|
|
1401
|
+
if (includeScope) line += ` [scope: ${scopeOf(ctx, f)}]`;
|
|
1402
|
+
return line;
|
|
1403
|
+
});
|
|
1404
|
+
sections.push(`### Facts
|
|
1405
|
+
${lines.join("\n")}`);
|
|
1406
|
+
}
|
|
1407
|
+
if (ctx.preferences.length > 0) {
|
|
1408
|
+
const lines = ctx.preferences.map((p) => {
|
|
1409
|
+
const extras = [];
|
|
1410
|
+
if (includeScope) extras.push(`scope: ${scopeOf(ctx, p)}`);
|
|
1411
|
+
const strength = numberOf(p["strength"]);
|
|
1412
|
+
if (strength !== null && strength !== 0 && strength >= 0.7) {
|
|
1413
|
+
extras.push(`strength: ${oneDecimal(strength)}`);
|
|
1414
|
+
}
|
|
1415
|
+
return bulletWithExtras(String(p.content ?? ""), extras);
|
|
1416
|
+
});
|
|
1417
|
+
sections.push(`### Preferences
|
|
1418
|
+
${lines.join("\n")}`);
|
|
1419
|
+
}
|
|
1420
|
+
if (ctx.episodes.length > 0) {
|
|
1421
|
+
const lines = ctx.episodes.map((e) => {
|
|
1422
|
+
let line = `- ${String(e["summary"] ?? "")}`;
|
|
1423
|
+
if (includeScope) line += ` [scope: ${scopeOf(ctx, e)}]`;
|
|
1424
|
+
return line;
|
|
1425
|
+
});
|
|
1426
|
+
sections.push(`### Episodes
|
|
1427
|
+
${lines.join("\n")}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (ctx.emotions.length > 0) {
|
|
1430
|
+
const lines = ctx.emotions.map((em) => {
|
|
1431
|
+
const extras = [];
|
|
1432
|
+
if (includeScope) extras.push(`scope: ${scopeOf(ctx, em)}`);
|
|
1433
|
+
const intensity = numberOf(em["intensity"]);
|
|
1434
|
+
if (intensity !== null && intensity !== 0 && intensity >= 0.5) {
|
|
1435
|
+
extras.push(`intensity: ${oneDecimal(intensity)}`);
|
|
1436
|
+
}
|
|
1437
|
+
const text = `${String(em["emotion_type"] ?? "")}: ${String(em["context"] ?? "")}`;
|
|
1438
|
+
return bulletWithExtras(text, extras);
|
|
1439
|
+
});
|
|
1440
|
+
sections.push(`### Emotions
|
|
1441
|
+
${lines.join("\n")}`);
|
|
1442
|
+
}
|
|
1443
|
+
if (ctx.temporal_events.length > 0) {
|
|
1444
|
+
const lines = ctx.temporal_events.map((te) => {
|
|
1445
|
+
const extras = [];
|
|
1446
|
+
if (includeScope) extras.push(`scope: ${scopeOf(ctx, te)}`);
|
|
1447
|
+
const validUntil = isoDate(te["valid_until"]);
|
|
1448
|
+
if (validUntil !== null) extras.push(`valid until: ${validUntil}`);
|
|
1449
|
+
return bulletWithExtras(String(te.content ?? ""), extras);
|
|
1450
|
+
});
|
|
1451
|
+
sections.push(`### Temporal Events
|
|
1452
|
+
${lines.join("\n")}`);
|
|
1453
|
+
}
|
|
1454
|
+
if (includeConversationContext && ctx.conversation_context !== null) {
|
|
1455
|
+
const formatted = ctx.conversation_context["formatted_context"];
|
|
1456
|
+
if (typeof formatted === "string" && formatted !== "") {
|
|
1457
|
+
sections.push(`### Conversation History
|
|
1458
|
+
${formatted}`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const blocks = [];
|
|
1462
|
+
if (sections.length > 0) blocks.push(`## User Context
|
|
1463
|
+
${sections.join("\n\n")}`);
|
|
1464
|
+
const profileBlock = formatProfileBlock(ctx.profile);
|
|
1465
|
+
if (profileBlock !== null) blocks.push(profileBlock);
|
|
1466
|
+
const conversationsBlock = formatConversationsBlock(ctx.conversations);
|
|
1467
|
+
if (conversationsBlock !== null) blocks.push(conversationsBlock);
|
|
1468
|
+
return blocks.length > 0 ? blocks.join("\n\n") : "";
|
|
1469
|
+
}
|
|
1470
|
+
function formatProfileBlock(profile) {
|
|
1471
|
+
if (profile === null) return null;
|
|
1472
|
+
const attributes = profile["attributes"];
|
|
1473
|
+
const lines = [];
|
|
1474
|
+
if (typeof attributes === "object" && attributes !== null) {
|
|
1475
|
+
for (const [name, attr] of Object.entries(attributes)) {
|
|
1476
|
+
const value = typeof attr === "object" && attr !== null ? attr["value"] : void 0;
|
|
1477
|
+
let valueStr;
|
|
1478
|
+
if (Array.isArray(value)) valueStr = value.map((v) => String(v)).join(", ");
|
|
1479
|
+
else if (value === null || value === void 0) valueStr = "";
|
|
1480
|
+
else valueStr = String(value);
|
|
1481
|
+
lines.push(`- ${name}: ${valueStr}`.replace(/\s+$/, ""));
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
const bodyParts = [];
|
|
1485
|
+
if (lines.length > 0) bodyParts.push(lines.join("\n"));
|
|
1486
|
+
const overview = profile["overview"];
|
|
1487
|
+
if (typeof overview === "string" && overview !== "") bodyParts.push(overview);
|
|
1488
|
+
if (bodyParts.length === 0) return null;
|
|
1489
|
+
return `## Caller Profile
|
|
1490
|
+
${bodyParts.join("\n\n")}`;
|
|
1491
|
+
}
|
|
1492
|
+
function formatConversationsBlock(conversations) {
|
|
1493
|
+
if (conversations === null || conversations.length === 0) return null;
|
|
1494
|
+
const callBlocks = conversations.map((conv) => {
|
|
1495
|
+
const when = isoDate(conv["started_at"]) ?? isoDate(conv["last_message_at"]) ?? isoDate(conv["ended_at"]);
|
|
1496
|
+
let header = `### Call on ${when ?? "unknown date"}`;
|
|
1497
|
+
const convType = conv["conversation_type"];
|
|
1498
|
+
if (typeof convType === "string" && convType !== "") header += ` (${convType})`;
|
|
1499
|
+
const callLines = [header];
|
|
1500
|
+
const overview = overviewText(conv);
|
|
1501
|
+
if (overview !== null) callLines.push(`Overview: ${overview}`);
|
|
1502
|
+
const outcome = outcomeText(conv);
|
|
1503
|
+
if (outcome !== null) callLines.push(`Outcome: ${outcome}`);
|
|
1504
|
+
if (overview === null && outcome === null) {
|
|
1505
|
+
callLines.push(`Status: ${String(conv["summary_status"] ?? "pending")}`);
|
|
1506
|
+
}
|
|
1507
|
+
return callLines.join("\n");
|
|
1508
|
+
});
|
|
1509
|
+
return `## Previous Conversations
|
|
1510
|
+
${callBlocks.join("\n\n")}`;
|
|
1511
|
+
}
|
|
1512
|
+
function overviewText(conv) {
|
|
1513
|
+
const summary = conv["summary"];
|
|
1514
|
+
if (typeof summary !== "object" || summary === null || Array.isArray(summary)) return null;
|
|
1515
|
+
for (const key of ["narrative_summary", "overview", "narrative", "summary"]) {
|
|
1516
|
+
const val = summary[key];
|
|
1517
|
+
if (typeof val === "string" && val.trim() !== "") return val.trim();
|
|
1518
|
+
}
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
function outcomeText(conv) {
|
|
1522
|
+
const classification = conv["classification"];
|
|
1523
|
+
if (typeof classification !== "object" || classification === null || Array.isArray(classification)) {
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
const c = classification;
|
|
1527
|
+
const parts = [c["primary_category"], c["subcategory"], c["objective"]].filter((p) => typeof p === "string" && p.trim() !== "");
|
|
1528
|
+
return parts.length > 0 ? parts.join(" / ") : null;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/tool/as-tool.ts
|
|
1532
|
+
var VALID_SCOPES = ["conversation", "user", "customer", "client", "unified"];
|
|
1533
|
+
var BASE_DESCRIPTION = "Retrieve stored context (facts, preferences, recent episodes, emotions, temporal events) from Synap memory.";
|
|
1534
|
+
function defaultToolDescription(scope) {
|
|
1535
|
+
switch (scope) {
|
|
1536
|
+
case "conversation":
|
|
1537
|
+
return BASE_DESCRIPTION + " Scoped to a specific conversation. Call this when you need facts already established in the current conversation, or to reload context after a topic shift.";
|
|
1538
|
+
case "user":
|
|
1539
|
+
return BASE_DESCRIPTION + " Scoped to the user's long-term memory. Call this when you need to recall who the user is, their preferences, or their history across conversations.";
|
|
1540
|
+
case "customer":
|
|
1541
|
+
return BASE_DESCRIPTION + " Scoped to the customer/organization. Call this when you need facts about the customer org rather than an individual.";
|
|
1542
|
+
case "client":
|
|
1543
|
+
return BASE_DESCRIPTION + " Scoped to the integrating client/product. Call this for product-level knowledge (e.g. policies, documentation).";
|
|
1544
|
+
default:
|
|
1545
|
+
return BASE_DESCRIPTION + " Cross-scope: merges conversation, user, customer, and client memory in one call. Call this at the start of a turn when you need general grounding before responding.";
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function toolInputSchema(scope, options) {
|
|
1549
|
+
const props = {
|
|
1550
|
+
search_query: {
|
|
1551
|
+
type: "array",
|
|
1552
|
+
items: { type: "string" },
|
|
1553
|
+
description: "Optional list of search queries describing what context you need. Free-form natural language is fine. Omit to retrieve the most relevant recent context."
|
|
1554
|
+
},
|
|
1555
|
+
max_results: {
|
|
1556
|
+
type: "integer",
|
|
1557
|
+
minimum: 1,
|
|
1558
|
+
maximum: 50,
|
|
1559
|
+
description: "Maximum items to return (default 10)."
|
|
1560
|
+
},
|
|
1561
|
+
types: {
|
|
1562
|
+
type: "array",
|
|
1563
|
+
items: {
|
|
1564
|
+
type: "string",
|
|
1565
|
+
enum: ["facts", "preferences", "episodes", "emotions", "temporal_events"]
|
|
1566
|
+
},
|
|
1567
|
+
description: "Filter to specific memory categories. Omit to retrieve all."
|
|
1568
|
+
},
|
|
1569
|
+
mode: {
|
|
1570
|
+
type: "string",
|
|
1571
|
+
enum: ["fast", "accurate"],
|
|
1572
|
+
description: "Retrieval mode. 'fast' = low-latency (~50ms); 'accurate' = LLM-decomposed multi-query (~200-500ms). Default 'fast'."
|
|
1573
|
+
},
|
|
1574
|
+
precision_level: {
|
|
1575
|
+
type: "string",
|
|
1576
|
+
enum: ["high", "medium"],
|
|
1577
|
+
description: "Result filtering precision. 'high' (default) applies an extra relevance-refinement pass; 'medium' skips it for faster, less precisely filtered results (recall unaffected)."
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
let required = [];
|
|
1581
|
+
if (scope === "conversation" && !options.hasConversationId) {
|
|
1582
|
+
props["conversation_id"] = {
|
|
1583
|
+
type: "string",
|
|
1584
|
+
description: "The conversation id to fetch context for."
|
|
1585
|
+
};
|
|
1586
|
+
required = ["conversation_id"];
|
|
1587
|
+
}
|
|
1588
|
+
return { type: "object", properties: props, required, additionalProperties: false };
|
|
1589
|
+
}
|
|
1590
|
+
var RESULT_COLLECTIONS = ["facts", "preferences", "episodes", "emotions", "temporal_events"];
|
|
1591
|
+
async function invokeScopeFetch(params) {
|
|
1592
|
+
const { target, scope, userId, customerId, conversationId, callArgs } = params;
|
|
1593
|
+
const searchQuery = callArgs["search_query"];
|
|
1594
|
+
const rawMax = callArgs["max_results"];
|
|
1595
|
+
const maxResults = typeof rawMax === "number" && rawMax !== 0 ? Math.trunc(rawMax) : 10;
|
|
1596
|
+
const types = callArgs["types"];
|
|
1597
|
+
const mode = String(callArgs["mode"] ?? "") || "fast";
|
|
1598
|
+
const precisionLevel = String(callArgs["precision_level"] ?? "") || "high";
|
|
1599
|
+
const callConvId = (typeof callArgs["conversation_id"] === "string" && callArgs["conversation_id"] !== "" ? callArgs["conversation_id"] : void 0) ?? conversationId;
|
|
1600
|
+
const shared = {
|
|
1601
|
+
search_query: searchQuery,
|
|
1602
|
+
max_results: maxResults,
|
|
1603
|
+
types,
|
|
1604
|
+
mode,
|
|
1605
|
+
precision_level: precisionLevel
|
|
1606
|
+
};
|
|
1607
|
+
if (scope === "unified") {
|
|
1608
|
+
const response2 = await target.fetch({
|
|
1609
|
+
conversation_id: callConvId,
|
|
1610
|
+
user_id: userId,
|
|
1611
|
+
customer_id: customerId,
|
|
1612
|
+
...shared
|
|
1613
|
+
});
|
|
1614
|
+
return {
|
|
1615
|
+
formatted_context: response2.formatted_context,
|
|
1616
|
+
scopes_queried: response2.scopes_queried,
|
|
1617
|
+
total_items: response2.total_items
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
let response;
|
|
1621
|
+
if (scope === "conversation") {
|
|
1622
|
+
if (callConvId === void 0 || callConvId === "") {
|
|
1623
|
+
return { error: "conversation_id is required", items: [] };
|
|
1624
|
+
}
|
|
1625
|
+
response = await target.conversation.context.fetch({
|
|
1626
|
+
conversation_id: callConvId,
|
|
1627
|
+
...shared,
|
|
1628
|
+
user_id: userId
|
|
1629
|
+
});
|
|
1630
|
+
} else if (scope === "user") {
|
|
1631
|
+
response = await target.user.context.fetch({
|
|
1632
|
+
user_id: userId,
|
|
1633
|
+
conversation_id: callConvId,
|
|
1634
|
+
...shared,
|
|
1635
|
+
customer_id: customerId
|
|
1636
|
+
});
|
|
1637
|
+
} else if (scope === "customer") {
|
|
1638
|
+
response = await target.customer.context.fetch({
|
|
1639
|
+
customer_id: customerId,
|
|
1640
|
+
conversation_id: callConvId,
|
|
1641
|
+
...shared
|
|
1642
|
+
});
|
|
1643
|
+
} else {
|
|
1644
|
+
response = await target.client.context.fetch({
|
|
1645
|
+
conversation_id: callConvId,
|
|
1646
|
+
...shared
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
const out = {};
|
|
1650
|
+
for (const collection of RESULT_COLLECTIONS) {
|
|
1651
|
+
out[collection] = Array.isArray(response[collection]) ? response[collection] : [];
|
|
1652
|
+
}
|
|
1653
|
+
return out;
|
|
1654
|
+
}
|
|
1655
|
+
function buildTool(target, options = {}, onWarning = () => {
|
|
1656
|
+
}) {
|
|
1657
|
+
const scope = String(options.scope ?? "user").toLowerCase();
|
|
1658
|
+
if (!VALID_SCOPES.includes(scope)) {
|
|
1659
|
+
const sorted = [...VALID_SCOPES].sort();
|
|
1660
|
+
throw new InvalidInputError(
|
|
1661
|
+
`scope must be one of ['${sorted.join("', '")}'], got '${String(options.scope)}'`
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
const userId = options.user_id ?? options.userId;
|
|
1665
|
+
const customerId = options.customer_id ?? options.customerId;
|
|
1666
|
+
const conversationId = options.conversation_id ?? options.conversationId;
|
|
1667
|
+
if (scope === "user" && (userId === void 0 || userId === "")) {
|
|
1668
|
+
throw new InvalidInputError("scope='user' requires user_id");
|
|
1669
|
+
}
|
|
1670
|
+
if (scope === "customer" && (customerId === void 0 || customerId === "")) {
|
|
1671
|
+
throw new InvalidInputError("scope='customer' requires customer_id");
|
|
1672
|
+
}
|
|
1673
|
+
if (scope === "conversation" && (userId === void 0 || userId === "")) {
|
|
1674
|
+
onWarning(
|
|
1675
|
+
"as_tool(scope='conversation') without user_id: the SDK anticipation cache cannot apply per-user filtering. Pass user_id to enable the Section 15 privacy guarantee."
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
const style = String(options.style ?? "openai");
|
|
1679
|
+
if (style !== "openai" && style !== "anthropic") {
|
|
1680
|
+
throw new InvalidInputError(`style must be 'openai' or 'anthropic', got '${style}'`);
|
|
1681
|
+
}
|
|
1682
|
+
const toolName = options.name ?? `synap_fetch_${scope}_context`;
|
|
1683
|
+
const toolDesc = options.description ?? defaultToolDescription(scope);
|
|
1684
|
+
const schema = toolInputSchema(scope, { hasConversationId: conversationId !== void 0 });
|
|
1685
|
+
const handler = (callArgs = {}) => invokeScopeFetch({ target, scope, userId, customerId, conversationId, callArgs });
|
|
1686
|
+
if (style === "openai") {
|
|
1687
|
+
return {
|
|
1688
|
+
type: "function",
|
|
1689
|
+
function: { name: toolName, description: toolDesc, parameters: schema },
|
|
1690
|
+
handler
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
return { name: toolName, description: toolDesc, input_schema: schema, handler };
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/client.ts
|
|
1697
|
+
var SynapClient = class {
|
|
1698
|
+
// Native-private (`#`), not TypeScript `private`. TS `private` is erased at
|
|
1699
|
+
// compile time, so the field stays enumerable at runtime and a JS consumer
|
|
1700
|
+
// can still reach it. These two were public and made `client.transport
|
|
1701
|
+
// .credentials.apiKey` and the raw cache's `now`/`fire`/`invalidateEntity`
|
|
1702
|
+
// reachable, which is exactly what `client.cache` exists to prevent.
|
|
1703
|
+
#transport;
|
|
1704
|
+
#anticipationCache;
|
|
1705
|
+
user;
|
|
1706
|
+
customer;
|
|
1707
|
+
client;
|
|
1708
|
+
conversation;
|
|
1709
|
+
memories;
|
|
1710
|
+
credits;
|
|
1711
|
+
cache;
|
|
1712
|
+
instance;
|
|
1713
|
+
#closed = false;
|
|
1714
|
+
#initialized = false;
|
|
1715
|
+
#compactionSubscribers;
|
|
1716
|
+
constructor(options = {}) {
|
|
1717
|
+
const credentials = resolveCredentials(options);
|
|
1718
|
+
validateInstanceId(credentials.instanceId);
|
|
1719
|
+
this.#transport = new HttpTransport({
|
|
1720
|
+
credentials,
|
|
1721
|
+
...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
|
|
1722
|
+
...options.timeouts !== void 0 ? { timeouts: options.timeouts } : {},
|
|
1723
|
+
...options.retryPolicy !== void 0 ? { retryPolicy: options.retryPolicy } : {},
|
|
1724
|
+
...options.keepAlive !== void 0 ? { keepAlive: options.keepAlive } : {},
|
|
1725
|
+
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
1726
|
+
userAgent: `@maximem/synap-js-sdk/${SDK_VERSION}`
|
|
1727
|
+
});
|
|
1728
|
+
if (options.heartbeat) this.#transport.startHeartbeat();
|
|
1729
|
+
this.#anticipationCache = new AnticipationCache();
|
|
1730
|
+
this.user = createUserNamespace(this.#transport);
|
|
1731
|
+
this.customer = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "customer", o2) } };
|
|
1732
|
+
this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2) } };
|
|
1733
|
+
this.#compactionSubscribers = new CompactionSubscribers();
|
|
1734
|
+
this.conversation = createConversationNamespace(
|
|
1735
|
+
this.#transport,
|
|
1736
|
+
this.#compactionSubscribers,
|
|
1737
|
+
// Mirrors Python's `_invalidate_anticipation_on_write`, including its
|
|
1738
|
+
// gate: `invalidateEntity` is a no-op unless
|
|
1739
|
+
// SYNAP_SDK_CACHE_INVALIDATE_ON_WRITE is set, so default behaviour is
|
|
1740
|
+
// unchanged. Over-invalidating after a failed write is safe; it only
|
|
1741
|
+
// costs the next lookup a cold fetch.
|
|
1742
|
+
({ userId, customerId }) => {
|
|
1743
|
+
for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
|
|
1744
|
+
if (id) this.#anticipationCache.invalidateEntity(id);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
);
|
|
1748
|
+
this.memories = createMemoriesInterface(this.#transport);
|
|
1749
|
+
this.credits = createCreditsNamespace(this.#transport);
|
|
1750
|
+
this.cache = createCacheNamespace(this.#anticipationCache);
|
|
1751
|
+
this.instance = createInstanceNamespace(
|
|
1752
|
+
() => this.#transport.currentCredentials(),
|
|
1753
|
+
this.#anticipationCache
|
|
1754
|
+
);
|
|
1755
|
+
this.instance._internal.setCompactionDispatcher((conversationId, bundle) => {
|
|
1756
|
+
this.#compactionSubscribers.dispatch(conversationId, bundle, (error) => {
|
|
1757
|
+
this.#warn(`compaction subscriber threw: ${String(error)}`);
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
// ── Deprecated camelCase surface, carried over from the 0.3.x wrapper ───────
|
|
1762
|
+
// These return the NORMALISED shape, unlike their namespaced equivalents.
|
|
1763
|
+
/** @deprecated Use `client.user.context.fetch()`. Note it returns raw snake_case. */
|
|
1764
|
+
async fetchUserContext(options = {}) {
|
|
1765
|
+
return normalizeContextResponse(await fetchContext(this.#transport, "user", options));
|
|
1766
|
+
}
|
|
1767
|
+
/** @deprecated Use `client.customer.context.fetch()`. */
|
|
1768
|
+
async fetchCustomerContext(options = {}) {
|
|
1769
|
+
return normalizeContextResponse(await fetchContext(this.#transport, "customer", options));
|
|
1770
|
+
}
|
|
1771
|
+
/** @deprecated Use `client.client.context.fetch()`. */
|
|
1772
|
+
async fetchClientContext(options = {}) {
|
|
1773
|
+
return normalizeContextResponse(await fetchContext(this.#transport, "client", options));
|
|
1774
|
+
}
|
|
1775
|
+
/** @deprecated Use `client.conversation.context.get_context_for_prompt()`. */
|
|
1776
|
+
async getContextForPrompt(options = {}) {
|
|
1777
|
+
return this.conversation.context.get_context_for_prompt(options);
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* @deprecated Use `client.user.context.fetch()` and read the typed collections.
|
|
1781
|
+
*
|
|
1782
|
+
* Flattens every context type into one list, exactly as the 0.3.x bridge did.
|
|
1783
|
+
*/
|
|
1784
|
+
async searchMemory(options) {
|
|
1785
|
+
const userId = options.user_id ?? options.userId;
|
|
1786
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
1787
|
+
if (options.query === void 0 || options.query === null) {
|
|
1788
|
+
throw new InvalidInputError("query is required");
|
|
1789
|
+
}
|
|
1790
|
+
const context = await fetchContext(this.#transport, "user", {
|
|
1791
|
+
user_id: userId,
|
|
1792
|
+
...pick2(options, "customer_id", "customerId") !== void 0 ? { customer_id: pick2(options, "customer_id", "customerId") } : {},
|
|
1793
|
+
...pick2(options, "conversation_id", "conversationId") !== void 0 ? { conversation_id: pick2(options, "conversation_id", "conversationId") } : {},
|
|
1794
|
+
search_query: [options.query],
|
|
1795
|
+
max_results: pick2(options, "max_results", "maxResults") ?? 10,
|
|
1796
|
+
types: options.types ?? ["all"],
|
|
1797
|
+
...options.mode !== void 0 ? { mode: options.mode } : {}
|
|
1798
|
+
});
|
|
1799
|
+
const results = flattenContextItems(context);
|
|
1800
|
+
return { results, count: results.length };
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* @deprecated Use `client.user.context.fetch()` with an empty `search_query`.
|
|
1804
|
+
*
|
|
1805
|
+
* Note the default of 100, not 10: it matches the 0.3.x bridge.
|
|
1806
|
+
*/
|
|
1807
|
+
async getMemories(options) {
|
|
1808
|
+
const userId = options.user_id ?? options.userId;
|
|
1809
|
+
if (!userId) throw new InvalidInputError("user_id is required");
|
|
1810
|
+
const context = await fetchContext(this.#transport, "user", {
|
|
1811
|
+
user_id: userId,
|
|
1812
|
+
...pick2(options, "customer_id", "customerId") !== void 0 ? { customer_id: pick2(options, "customer_id", "customerId") } : {},
|
|
1813
|
+
...pick2(options, "conversation_id", "conversationId") !== void 0 ? { conversation_id: pick2(options, "conversation_id", "conversationId") } : {},
|
|
1814
|
+
search_query: [],
|
|
1815
|
+
max_results: pick2(options, "max_results", "maxResults") ?? 100,
|
|
1816
|
+
types: options.types ?? ["all"],
|
|
1817
|
+
...options.mode !== void 0 ? { mode: options.mode } : {}
|
|
1818
|
+
});
|
|
1819
|
+
const results = flattenContextItems(context);
|
|
1820
|
+
return { results, count: results.length };
|
|
1821
|
+
}
|
|
1822
|
+
/** @deprecated Use `client.memories.create()`. */
|
|
1823
|
+
async addMemory(options) {
|
|
1824
|
+
return this.memories.create(options);
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* @deprecated Use `client.memories.delete(memoryId)`.
|
|
1828
|
+
*
|
|
1829
|
+
* The `{ userId }` form is gone: it relied on a process-local id list and so
|
|
1830
|
+
* deleted nothing in serverless while reporting success (gotcha G-F).
|
|
1831
|
+
*/
|
|
1832
|
+
async deleteMemory(options) {
|
|
1833
|
+
return this.memories.delete(options.memory_id ?? options.memoryId ?? "");
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Resolve `client_id` and `instance_id` from the API key.
|
|
1837
|
+
*
|
|
1838
|
+
* Mirrors Python's `initialize()`. Calling it is optional here (unlike
|
|
1839
|
+
* Python, which raises "SDK not initialized" from `_ensure_initialized`)
|
|
1840
|
+
* because every HTTP route authenticates on the API key alone. It matters
|
|
1841
|
+
* for the anticipation cache: a bundle shared at client scope is filtered on
|
|
1842
|
+
* `client_id`, and an empty one fails that filter for every bundle. That was
|
|
1843
|
+
* a silent cache-scope bug in Python before whoami bootstrapping existed.
|
|
1844
|
+
*
|
|
1845
|
+
* Best-effort and idempotent. A whoami failure leaves whatever the
|
|
1846
|
+
* constructor resolved and is not fatal.
|
|
1847
|
+
*/
|
|
1848
|
+
async initialize() {
|
|
1849
|
+
if (this.#initialized) return;
|
|
1850
|
+
this.#initialized = true;
|
|
1851
|
+
const credentials = this.#transport.currentCredentials();
|
|
1852
|
+
if (credentials.clientId !== "" && credentials.instanceId !== "") return;
|
|
1853
|
+
try {
|
|
1854
|
+
const whoami = await this.#transport.request("whoami", {});
|
|
1855
|
+
const resolvedClientId = String(whoami["client_id"] ?? "");
|
|
1856
|
+
const resolvedInstanceId = String(whoami["instance_id"] ?? "");
|
|
1857
|
+
this.#transport.updateCredentials({
|
|
1858
|
+
...resolvedClientId !== "" && credentials.clientId === "" ? { clientId: resolvedClientId } : {},
|
|
1859
|
+
...resolvedInstanceId !== "" && credentials.instanceId === "" ? { instanceId: resolvedInstanceId } : {}
|
|
1860
|
+
});
|
|
1861
|
+
} catch {
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* @deprecated Use `initialize()`, which is the Python name. `init()` was a
|
|
1866
|
+
* JS-only spelling with no Python counterpart; it now forwards, so a 0.3.x
|
|
1867
|
+
* startup script keeps working.
|
|
1868
|
+
*/
|
|
1869
|
+
async init() {
|
|
1870
|
+
return this.initialize();
|
|
1871
|
+
}
|
|
1872
|
+
/** The resolved instance id. Empty until `initialize()` resolves it. */
|
|
1873
|
+
get instance_id() {
|
|
1874
|
+
return this.#transport.currentCredentials().instanceId;
|
|
1875
|
+
}
|
|
1876
|
+
/** The resolved client id. Empty until `initialize()` resolves it. */
|
|
1877
|
+
get client_id() {
|
|
1878
|
+
return this.#transport.currentCredentials().clientId;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Update configuration before `initialize()`.
|
|
1882
|
+
*
|
|
1883
|
+
* Mirrors Python's `configure()`, including its refusal to reconfigure after
|
|
1884
|
+
* initialization: the transport and cache are already built by then and a
|
|
1885
|
+
* half-applied config is worse than a rejected one.
|
|
1886
|
+
*
|
|
1887
|
+
* The Python-only keys (`storage_path`, `cache_backend`,
|
|
1888
|
+
* `session_timeout_minutes`, `logger`) are accepted and ignored rather than
|
|
1889
|
+
* rejected, because this SDK has no SQLite cache backend and no global
|
|
1890
|
+
* logger to swap. Ignoring them keeps a shared config object portable
|
|
1891
|
+
* between the two SDKs.
|
|
1892
|
+
*/
|
|
1893
|
+
configure(options = {}) {
|
|
1894
|
+
if (this.#initialized) {
|
|
1895
|
+
throw new InvalidInputError("Cannot reconfigure after initialization");
|
|
1896
|
+
}
|
|
1897
|
+
if (options.timeouts !== void 0) this.#transport.setTimeouts(options.timeouts);
|
|
1898
|
+
if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
|
|
1899
|
+
this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Fetch and merge context across every scope with an identifier, in one call.
|
|
1904
|
+
*
|
|
1905
|
+
* Mirrors Python's `sdk.fetch()`. The three behaviours worth knowing:
|
|
1906
|
+
*
|
|
1907
|
+
* - Scope fetches run in parallel and a FAILED scope is dropped with a
|
|
1908
|
+
* warning rather than failing the call, so partial context still gets
|
|
1909
|
+
* returned. The one exception is `InvalidInputError`: a bad request must
|
|
1910
|
+
* surface instead of degrading into a silently-empty context.
|
|
1911
|
+
* - `user_id` is threaded into the conversation-scope sub-fetch so the
|
|
1912
|
+
* anticipation cache can apply its per-user filter.
|
|
1913
|
+
* - The summary-mode parameters go ONLY to the user-scope sub-fetch. The
|
|
1914
|
+
* other three routes return 422 for `context_mode="conversation-summary"`,
|
|
1915
|
+
* and that rejection would be swallowed by the drop-failed-scope rule.
|
|
1916
|
+
*/
|
|
1917
|
+
async fetch(options = {}) {
|
|
1918
|
+
const {
|
|
1919
|
+
conversation_id: conversationId,
|
|
1920
|
+
user_id: userId,
|
|
1921
|
+
customer_id: customerId,
|
|
1922
|
+
search_query: searchQuery,
|
|
1923
|
+
max_results: maxResults = 20,
|
|
1924
|
+
types,
|
|
1925
|
+
mode = "fast",
|
|
1926
|
+
precision_level: precisionLevel = "high",
|
|
1927
|
+
include_conversation_context: includeConversationContext = true,
|
|
1928
|
+
scopes,
|
|
1929
|
+
include_scope_labels: includeScopeLabels = false,
|
|
1930
|
+
context_mode: contextMode = "in-conversation",
|
|
1931
|
+
include_profile: includeProfile = true,
|
|
1932
|
+
last_n_conversations: lastNConversations = 1
|
|
1933
|
+
} = options;
|
|
1934
|
+
validateConversationId(conversationId);
|
|
1935
|
+
const wants = (scope) => scopes === void 0 || scopes.includes(scope);
|
|
1936
|
+
const shared = {
|
|
1937
|
+
search_query: searchQuery,
|
|
1938
|
+
max_results: maxResults,
|
|
1939
|
+
types,
|
|
1940
|
+
mode,
|
|
1941
|
+
precision_level: precisionLevel
|
|
1942
|
+
};
|
|
1943
|
+
const labels = [];
|
|
1944
|
+
const tasks = [];
|
|
1945
|
+
if (conversationId !== void 0 && conversationId !== "" && wants("conversation")) {
|
|
1946
|
+
labels.push("conversation");
|
|
1947
|
+
tasks.push(this.conversation.context.fetch({
|
|
1948
|
+
conversation_id: conversationId,
|
|
1949
|
+
...shared,
|
|
1950
|
+
user_id: userId,
|
|
1951
|
+
customer_id: customerId
|
|
1952
|
+
}));
|
|
1953
|
+
}
|
|
1954
|
+
if (userId !== void 0 && userId !== "" && wants("user")) {
|
|
1955
|
+
labels.push("user");
|
|
1956
|
+
tasks.push(this.user.context.fetch({
|
|
1957
|
+
user_id: userId,
|
|
1958
|
+
conversation_id: conversationId,
|
|
1959
|
+
...shared,
|
|
1960
|
+
customer_id: customerId,
|
|
1961
|
+
context_mode: contextMode,
|
|
1962
|
+
include_profile: includeProfile,
|
|
1963
|
+
last_n_conversations: lastNConversations
|
|
1964
|
+
}));
|
|
1965
|
+
}
|
|
1966
|
+
if (customerId !== void 0 && customerId !== "" && wants("customer")) {
|
|
1967
|
+
labels.push("customer");
|
|
1968
|
+
tasks.push(this.customer.context.fetch({
|
|
1969
|
+
customer_id: customerId,
|
|
1970
|
+
conversation_id: conversationId,
|
|
1971
|
+
...shared
|
|
1972
|
+
}));
|
|
1973
|
+
}
|
|
1974
|
+
if (scopes !== void 0 && scopes.includes("client")) {
|
|
1975
|
+
labels.push("client");
|
|
1976
|
+
tasks.push(this.client.context.fetch({
|
|
1977
|
+
conversation_id: conversationId,
|
|
1978
|
+
...shared
|
|
1979
|
+
}));
|
|
1980
|
+
}
|
|
1981
|
+
if (tasks.length === 0) {
|
|
1982
|
+
const empty = mergeScopeResults([]);
|
|
1983
|
+
return empty;
|
|
1984
|
+
}
|
|
1985
|
+
const settled = await Promise.allSettled(tasks);
|
|
1986
|
+
const successful = [];
|
|
1987
|
+
for (const [i, result] of settled.entries()) {
|
|
1988
|
+
const label = labels[i];
|
|
1989
|
+
if (result.status === "fulfilled") {
|
|
1990
|
+
successful.push([label, result.value]);
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
if (result.reason instanceof InvalidInputError) throw result.reason;
|
|
1994
|
+
this.#warn(`Scope '${label}' fetch failed in unified fetch (non-fatal): ${String(result.reason)}`);
|
|
1995
|
+
}
|
|
1996
|
+
const merged = mergeScopeResults(successful);
|
|
1997
|
+
if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
|
|
1998
|
+
try {
|
|
1999
|
+
merged.conversation_context = await this.conversation.context.get_context_for_prompt({
|
|
2000
|
+
conversation_id: conversationId
|
|
2001
|
+
});
|
|
2002
|
+
} catch {
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
merged.formatted_context = formatForPrompt(merged, {
|
|
2006
|
+
includeScope: includeScopeLabels,
|
|
2007
|
+
includeConversationContext
|
|
2008
|
+
});
|
|
2009
|
+
return merged;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* An LLM-ready tool definition that fetches Synap context.
|
|
2013
|
+
*
|
|
2014
|
+
* Mirrors Python's `as_tool()`. The scope identifiers are closed over, so
|
|
2015
|
+
* the model cannot choose whose memory to read.
|
|
2016
|
+
*/
|
|
2017
|
+
as_tool(options = {}) {
|
|
2018
|
+
return buildTool(this, options, (message) => this.#warn(message));
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* A read-only diagnostic view of the in-process anticipation cache.
|
|
2022
|
+
*
|
|
2023
|
+
* Mirrors Python's `anticipation_cache_snapshot()`. Returns the same
|
|
2024
|
+
* snake_case shape, since this output gets logged and compared across SDKs.
|
|
2025
|
+
*/
|
|
2026
|
+
anticipation_cache_snapshot() {
|
|
2027
|
+
return this.#anticipationCache.snapshot();
|
|
2028
|
+
}
|
|
2029
|
+
/** Python logs via `logger.warning`; here it is one overridable seam. */
|
|
2030
|
+
#warn(message) {
|
|
2031
|
+
console.warn(`[synap] ${message}`);
|
|
2032
|
+
}
|
|
2033
|
+
/** Release timers and connections. Safe to call more than once. */
|
|
2034
|
+
async shutdown() {
|
|
2035
|
+
if (this.#closed) return;
|
|
2036
|
+
this.#closed = true;
|
|
2037
|
+
await this.instance.stop_listening();
|
|
2038
|
+
this.#compactionSubscribers.removeAll();
|
|
2039
|
+
this.#transport.close();
|
|
2040
|
+
this.#anticipationCache.clear();
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
function pick2(o2, snake, camel) {
|
|
2044
|
+
const a = o2[snake];
|
|
2045
|
+
return a !== void 0 ? a : o2[camel];
|
|
2046
|
+
}
|
|
2047
|
+
function resolveCredentials(options) {
|
|
2048
|
+
const apiKey = options.apiKey ?? getEnv("SYNAP_API_KEY");
|
|
2049
|
+
if (!apiKey) {
|
|
2050
|
+
throw new InvalidInputError(
|
|
2051
|
+
"No Synap API key found. Set SYNAP_API_KEY in your environment, or pass apiKey to the SynapClient constructor."
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
return {
|
|
2055
|
+
apiKey,
|
|
2056
|
+
clientId: options.clientId ?? getEnv("SYNAP_CLIENT_ID") ?? "",
|
|
2057
|
+
instanceId: options.instanceId ?? getEnv("SYNAP_INSTANCE_ID") ?? ""
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
export {
|
|
2061
|
+
ANY_SCOPE,
|
|
2062
|
+
AgentUnavailableError,
|
|
2063
|
+
AnticipationCache,
|
|
2064
|
+
AuthenticationError,
|
|
2065
|
+
BM25,
|
|
2066
|
+
CONTRACT,
|
|
2067
|
+
ConflictError,
|
|
2068
|
+
ConnectionError,
|
|
2069
|
+
ContextNotFoundError,
|
|
2070
|
+
DEFAULT_BASE_URL,
|
|
2071
|
+
DEFAULT_DOCUMENT_TYPE,
|
|
2072
|
+
DEFAULT_INGEST_MODE,
|
|
2073
|
+
DEFAULT_RETRY_POLICY,
|
|
2074
|
+
DEFAULT_TIMEOUTS,
|
|
2075
|
+
DOCUMENT_TYPES,
|
|
2076
|
+
ENDPOINTS,
|
|
2077
|
+
HttpTransport,
|
|
2078
|
+
INGEST_MODES,
|
|
2079
|
+
ITEM_COLLECTIONS,
|
|
2080
|
+
InsufficientCreditsError,
|
|
2081
|
+
InvalidConversationIdError,
|
|
2082
|
+
InvalidInputError,
|
|
2083
|
+
InvalidInstanceIdError,
|
|
2084
|
+
ListeningAlreadyActiveError,
|
|
2085
|
+
ListeningNotActiveError,
|
|
2086
|
+
NetworkTimeoutError,
|
|
2087
|
+
PermanentError,
|
|
2088
|
+
RateLimitError,
|
|
2089
|
+
SDKError,
|
|
2090
|
+
SDK_VERSION,
|
|
2091
|
+
ServiceUnavailableError,
|
|
2092
|
+
SessionExpiredError,
|
|
2093
|
+
SynapClient,
|
|
2094
|
+
SynapError,
|
|
2095
|
+
SynapPermanentError,
|
|
2096
|
+
SynapTransientError,
|
|
2097
|
+
THRESHOLDS,
|
|
2098
|
+
TranscriptConflictError,
|
|
2099
|
+
TransientError,
|
|
2100
|
+
flattenContextItems,
|
|
2101
|
+
formatForPrompt,
|
|
2102
|
+
isOutcomeUnknown,
|
|
2103
|
+
isRecallQuery,
|
|
2104
|
+
isSynapError,
|
|
2105
|
+
mergeScopeResults,
|
|
2106
|
+
resolvePath,
|
|
2107
|
+
shouldRetry,
|
|
2108
|
+
stem,
|
|
2109
|
+
tokenize
|
|
2110
|
+
};
|
|
2111
|
+
//# sourceMappingURL=index.js.map
|