@maximem/synap-js-sdk 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -234,6 +234,12 @@ interface TimeoutConfig {
234
234
  connect: number;
235
235
  read: number;
236
236
  write: number;
237
+ /**
238
+ * gRPC stream idle timeout. Not used by the HTTP transport; the stream
239
+ * client reads it. Present so a config object round-trips between the two
240
+ * SDKs, and because Python's TimeoutConfig has it.
241
+ */
242
+ streamIdle: number;
237
243
  }
238
244
  declare const DEFAULT_TIMEOUTS: TimeoutConfig;
239
245
  interface Credentials {
@@ -987,6 +993,16 @@ interface SendMessageOptions {
987
993
  }
988
994
  interface RecordThinkingOptions {
989
995
  content: string;
996
+ /**
997
+ * Ordinal of this thought within the current turn. Sent as a `step_index`
998
+ * metadata key, which is how Python's `record_thinking` transmits it: the
999
+ * proto has no dedicated field.
1000
+ */
1001
+ step_index?: number;
1002
+ stepIndex?: number;
1003
+ /** Free-form label for the kind of reasoning. Sent as `thought_type`. */
1004
+ thought_type?: string;
1005
+ thoughtType?: string;
990
1006
  conversation_id?: string;
991
1007
  conversationId?: string;
992
1008
  user_id?: string;
@@ -1133,6 +1149,16 @@ type ToolDefinition = OpenAiToolDefinition | AnthropicToolDefinition;
1133
1149
  * They return different shapes on purpose. See gotcha G-B.
1134
1150
  */
1135
1151
 
1152
+ /** Levels the SDK emits. Mirrors the subset of Python's logging it uses. */
1153
+ type SynapLogLevel = 'debug' | 'info' | 'warn' | 'error';
1154
+ /**
1155
+ * Receives every diagnostic the SDK would otherwise write to the console.
1156
+ *
1157
+ * Python routes these through the stdlib `logging` module, which is why it
1158
+ * takes `log_level` and `logger`. There is no global logger in JavaScript, so
1159
+ * the SDK takes the sink directly instead of a level plus a framework.
1160
+ */
1161
+ type SynapLogger = (level: SynapLogLevel, message: string) => void;
1136
1162
  interface SynapClientOptions {
1137
1163
  /**
1138
1164
  * The four identity fields are `| undefined` rather than plain optional so a
@@ -1144,6 +1170,9 @@ interface SynapClientOptions {
1144
1170
  clientId?: string | undefined;
1145
1171
  instanceId?: string | undefined;
1146
1172
  baseUrl?: string | undefined;
1173
+ /** Python spells this `api_base_url`; both are accepted so one config
1174
+ * object works against either SDK. */
1175
+ api_base_url?: string | undefined;
1147
1176
  timeouts?: HttpTransportOptions['timeouts'];
1148
1177
  retryPolicy?: HttpTransportOptions['retryPolicy'];
1149
1178
  keepAlive?: HttpTransportOptions['keepAlive'];
@@ -1155,6 +1184,29 @@ interface SynapClientOptions {
1155
1184
  */
1156
1185
  heartbeat?: boolean;
1157
1186
  fetchImpl?: typeof fetch;
1187
+ /**
1188
+ * Splice locally-recorded turns into the next conversation-scope fetch, so a
1189
+ * just-written turn is visible before the server compacts. Defaults to true,
1190
+ * matching Python's `st_verbatim_overlay`. `SYNAP_ST_VERBATIM_OVERLAY`
1191
+ * overrides this either way.
1192
+ */
1193
+ st_verbatim_overlay?: boolean | undefined;
1194
+ stVerbatimOverlay?: boolean | undefined;
1195
+ /**
1196
+ * Let the SDK answer `get_context_for_prompt` and `get_compacted` from its
1197
+ * own short-term store when the conversation is warm, skipping the cloud
1198
+ * round trip. Off by default. `SYNAP_SDK_ST_AUTHORITATIVE` also enables it.
1199
+ *
1200
+ * This is the COST switch. `st_verbatim_overlay` is the correctness one and
1201
+ * is independent of it.
1202
+ */
1203
+ sdk_st_authoritative?: boolean | undefined;
1204
+ sdkStAuthoritative?: boolean | undefined;
1205
+ /**
1206
+ * Where the SDK's diagnostics go. Defaults to `console.warn` prefixed with
1207
+ * `[synap]`. Pass a no-op to silence them, or your own logger to route them.
1208
+ */
1209
+ logger?: SynapLogger | undefined;
1158
1210
  /**
1159
1211
  * Bypass the process registry and build a genuinely separate client.
1160
1212
  *
@@ -1188,8 +1240,8 @@ interface ConfigureOptions {
1188
1240
  session_timeout_minutes?: number;
1189
1241
  /** Accepted and ignored. Python-only. */
1190
1242
  log_level?: string;
1191
- /** Accepted and ignored. Python-only. */
1192
- logger?: unknown;
1243
+ /** Replaces the diagnostic sink, same as the constructor option. */
1244
+ logger?: SynapLogger | undefined;
1193
1245
  }
1194
1246
  /** Options for the cross-scope `fetch()`. Snake_case, matching Python's kwargs. */
1195
1247
  interface UnifiedFetchOptions {
@@ -1315,11 +1367,12 @@ declare class SynapClient {
1315
1367
  * initialization: the transport and cache are already built by then and a
1316
1368
  * half-applied config is worse than a rejected one.
1317
1369
  *
1318
- * The Python-only keys (`storage_path`, `cache_backend`,
1319
- * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
1320
- * rejected, because this SDK has no SQLite cache backend and no global
1321
- * logger to swap. Ignoring them keeps a shared config object portable
1322
- * between the two SDKs.
1370
+ * `logger` is honoured here as well as on the constructor. The remaining
1371
+ * Python-only keys (`storage_path`, `cache_backend`,
1372
+ * `session_timeout_minutes`, `log_level`) are accepted and ignored rather
1373
+ * than rejected, because this SDK has no SQLite cache backend and no global
1374
+ * logging framework whose level there would be to set. Ignoring them keeps
1375
+ * a shared config object portable between the two SDKs.
1323
1376
  */
1324
1377
  configure(options?: ConfigureOptions): void;
1325
1378
  /**
@@ -1654,4 +1707,4 @@ interface SynapSdkLike {
1654
1707
  };
1655
1708
  }
1656
1709
 
1657
- export { AgentUnavailableError, type AnthropicToolDefinition, AnticipationCacheSnapshot, type AsToolOptions, AuthenticationError, BM25, type BatchCreateResult, COMPACTION_LEVELS, CONTRACT, type CacheNamespace, type CacheStats, type CompactOptions, type CompactionCallback, type CompactionLevel, type ConfigureOptions, ConflictError, ConnectionError, type ContextMetadata, ContextNotFoundError, type ConversationContext, type ConversationNamespace, type CreateFromFileOptions, type CreateMemoryOptions, type CreateMemoryResult, type Credentials, type CreditBalance, type CreditBucket, type CreditEstimate, type CreditLedgerEntry, type CreditLedgerPage, type CreditsNamespace, DEFAULT_BASE_URL, DEFAULT_DOCUMENT_TYPE, DEFAULT_INGEST_MODE, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUTS, DOCUMENT_TYPES, type DocumentType, ENDPOINTS, type Emotion, type EndpointName, type EndpointSpec, type Episode, type EstimateOptions, type Fact, type FetchOptions, type FlatMemory, type FormatOptions, HttpTransport, type HttpTransportOptions, INGEST_MODES, ITEM_COLLECTIONS, type IngestMode, type IngestTranscriptOptions, type InstanceNamespace, InsufficientCreditsError, InvalidConversationIdError, InvalidInputError, InvalidInstanceIdError, type ItemCollection, type Json, type LedgerOptions, type ListenOptions, ListeningAlreadyActiveError, ListeningNotActiveError, type MemoriesInterface, NetworkTimeoutError, type NormalisedContext, type OpenAiToolDefinition, PermanentError, type Preference, RateLimitError, type RawContext, type RawContextItem, type RawConversationContext, type RecordMessageOptions, type RecordThinkingOptions, type RedeemResult, type RequestOptions, type RetryPolicy, SDKError, SDK_VERSION, type Scope, type SendMessageOptions, ServiceUnavailableError, SessionExpiredError, SynapClient, type SynapClientOptions, SynapError, type SynapErrorCode, type SynapErrorOptions, SynapPermanentError, type SynapSdkLike, SynapTransientError, THRESHOLDS, type TemporalEvent, type TemporalFields, type TimeoutConfig, type ToolDefinition, type ToolHandler, type ToolScope, type ToolStyle, TranscriptConflictError, type TranscriptIngestResult, type TranscriptTurn, TransientError, type UnifiedContext, type UnifiedFetchOptions, type UpdateMemoryOptions, type UserNamespace, type WaitOptions, type WriteInvalidator, flattenContextItems, formatForPrompt, isOutcomeUnknown, isRecallQuery, isSynapError, mergeScopeResults, resolvePath, shouldRetry, stem, tokenize };
1710
+ export { AgentUnavailableError, type AnthropicToolDefinition, AnticipationCacheSnapshot, type AsToolOptions, AuthenticationError, BM25, type BatchCreateResult, COMPACTION_LEVELS, CONTRACT, type CacheNamespace, type CacheStats, type CompactOptions, type CompactionCallback, type CompactionLevel, type ConfigureOptions, ConflictError, ConnectionError, type ContextMetadata, ContextNotFoundError, type ConversationContext, type ConversationNamespace, type CreateFromFileOptions, type CreateMemoryOptions, type CreateMemoryResult, type Credentials, type CreditBalance, type CreditBucket, type CreditEstimate, type CreditLedgerEntry, type CreditLedgerPage, type CreditsNamespace, DEFAULT_BASE_URL, DEFAULT_DOCUMENT_TYPE, DEFAULT_INGEST_MODE, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUTS, DOCUMENT_TYPES, type DocumentType, ENDPOINTS, type Emotion, type EndpointName, type EndpointSpec, type Episode, type EstimateOptions, type Fact, type FetchOptions, type FlatMemory, type FormatOptions, HttpTransport, type HttpTransportOptions, INGEST_MODES, ITEM_COLLECTIONS, type IngestMode, type IngestTranscriptOptions, type InstanceNamespace, InsufficientCreditsError, InvalidConversationIdError, InvalidInputError, InvalidInstanceIdError, type ItemCollection, type Json, type LedgerOptions, type ListenOptions, ListeningAlreadyActiveError, ListeningNotActiveError, type MemoriesInterface, NetworkTimeoutError, type NormalisedContext, type OpenAiToolDefinition, PermanentError, type Preference, RateLimitError, type RawContext, type RawContextItem, type RawConversationContext, type RecordMessageOptions, type RecordThinkingOptions, type RedeemResult, type RequestOptions, type RetryPolicy, SDKError, SDK_VERSION, type Scope, type SendMessageOptions, ServiceUnavailableError, SessionExpiredError, SynapClient, type SynapClientOptions, SynapError, type SynapErrorCode, type SynapErrorOptions, type SynapLogLevel, type SynapLogger, SynapPermanentError, type SynapSdkLike, SynapTransientError, THRESHOLDS, type TemporalEvent, type TemporalFields, type TimeoutConfig, type ToolDefinition, type ToolHandler, type ToolScope, type ToolStyle, TranscriptConflictError, type TranscriptIngestResult, type TranscriptTurn, TransientError, type UnifiedContext, type UnifiedFetchOptions, type UpdateMemoryOptions, type UserNamespace, type WaitOptions, type WriteInvalidator, flattenContextItems, formatForPrompt, isOutcomeUnknown, isRecallQuery, isSynapError, mergeScopeResults, resolvePath, shouldRetry, stem, tokenize };
package/dist/index.d.ts CHANGED
@@ -234,6 +234,12 @@ interface TimeoutConfig {
234
234
  connect: number;
235
235
  read: number;
236
236
  write: number;
237
+ /**
238
+ * gRPC stream idle timeout. Not used by the HTTP transport; the stream
239
+ * client reads it. Present so a config object round-trips between the two
240
+ * SDKs, and because Python's TimeoutConfig has it.
241
+ */
242
+ streamIdle: number;
237
243
  }
238
244
  declare const DEFAULT_TIMEOUTS: TimeoutConfig;
239
245
  interface Credentials {
@@ -987,6 +993,16 @@ interface SendMessageOptions {
987
993
  }
988
994
  interface RecordThinkingOptions {
989
995
  content: string;
996
+ /**
997
+ * Ordinal of this thought within the current turn. Sent as a `step_index`
998
+ * metadata key, which is how Python's `record_thinking` transmits it: the
999
+ * proto has no dedicated field.
1000
+ */
1001
+ step_index?: number;
1002
+ stepIndex?: number;
1003
+ /** Free-form label for the kind of reasoning. Sent as `thought_type`. */
1004
+ thought_type?: string;
1005
+ thoughtType?: string;
990
1006
  conversation_id?: string;
991
1007
  conversationId?: string;
992
1008
  user_id?: string;
@@ -1133,6 +1149,16 @@ type ToolDefinition = OpenAiToolDefinition | AnthropicToolDefinition;
1133
1149
  * They return different shapes on purpose. See gotcha G-B.
1134
1150
  */
1135
1151
 
1152
+ /** Levels the SDK emits. Mirrors the subset of Python's logging it uses. */
1153
+ type SynapLogLevel = 'debug' | 'info' | 'warn' | 'error';
1154
+ /**
1155
+ * Receives every diagnostic the SDK would otherwise write to the console.
1156
+ *
1157
+ * Python routes these through the stdlib `logging` module, which is why it
1158
+ * takes `log_level` and `logger`. There is no global logger in JavaScript, so
1159
+ * the SDK takes the sink directly instead of a level plus a framework.
1160
+ */
1161
+ type SynapLogger = (level: SynapLogLevel, message: string) => void;
1136
1162
  interface SynapClientOptions {
1137
1163
  /**
1138
1164
  * The four identity fields are `| undefined` rather than plain optional so a
@@ -1144,6 +1170,9 @@ interface SynapClientOptions {
1144
1170
  clientId?: string | undefined;
1145
1171
  instanceId?: string | undefined;
1146
1172
  baseUrl?: string | undefined;
1173
+ /** Python spells this `api_base_url`; both are accepted so one config
1174
+ * object works against either SDK. */
1175
+ api_base_url?: string | undefined;
1147
1176
  timeouts?: HttpTransportOptions['timeouts'];
1148
1177
  retryPolicy?: HttpTransportOptions['retryPolicy'];
1149
1178
  keepAlive?: HttpTransportOptions['keepAlive'];
@@ -1155,6 +1184,29 @@ interface SynapClientOptions {
1155
1184
  */
1156
1185
  heartbeat?: boolean;
1157
1186
  fetchImpl?: typeof fetch;
1187
+ /**
1188
+ * Splice locally-recorded turns into the next conversation-scope fetch, so a
1189
+ * just-written turn is visible before the server compacts. Defaults to true,
1190
+ * matching Python's `st_verbatim_overlay`. `SYNAP_ST_VERBATIM_OVERLAY`
1191
+ * overrides this either way.
1192
+ */
1193
+ st_verbatim_overlay?: boolean | undefined;
1194
+ stVerbatimOverlay?: boolean | undefined;
1195
+ /**
1196
+ * Let the SDK answer `get_context_for_prompt` and `get_compacted` from its
1197
+ * own short-term store when the conversation is warm, skipping the cloud
1198
+ * round trip. Off by default. `SYNAP_SDK_ST_AUTHORITATIVE` also enables it.
1199
+ *
1200
+ * This is the COST switch. `st_verbatim_overlay` is the correctness one and
1201
+ * is independent of it.
1202
+ */
1203
+ sdk_st_authoritative?: boolean | undefined;
1204
+ sdkStAuthoritative?: boolean | undefined;
1205
+ /**
1206
+ * Where the SDK's diagnostics go. Defaults to `console.warn` prefixed with
1207
+ * `[synap]`. Pass a no-op to silence them, or your own logger to route them.
1208
+ */
1209
+ logger?: SynapLogger | undefined;
1158
1210
  /**
1159
1211
  * Bypass the process registry and build a genuinely separate client.
1160
1212
  *
@@ -1188,8 +1240,8 @@ interface ConfigureOptions {
1188
1240
  session_timeout_minutes?: number;
1189
1241
  /** Accepted and ignored. Python-only. */
1190
1242
  log_level?: string;
1191
- /** Accepted and ignored. Python-only. */
1192
- logger?: unknown;
1243
+ /** Replaces the diagnostic sink, same as the constructor option. */
1244
+ logger?: SynapLogger | undefined;
1193
1245
  }
1194
1246
  /** Options for the cross-scope `fetch()`. Snake_case, matching Python's kwargs. */
1195
1247
  interface UnifiedFetchOptions {
@@ -1315,11 +1367,12 @@ declare class SynapClient {
1315
1367
  * initialization: the transport and cache are already built by then and a
1316
1368
  * half-applied config is worse than a rejected one.
1317
1369
  *
1318
- * The Python-only keys (`storage_path`, `cache_backend`,
1319
- * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
1320
- * rejected, because this SDK has no SQLite cache backend and no global
1321
- * logger to swap. Ignoring them keeps a shared config object portable
1322
- * between the two SDKs.
1370
+ * `logger` is honoured here as well as on the constructor. The remaining
1371
+ * Python-only keys (`storage_path`, `cache_backend`,
1372
+ * `session_timeout_minutes`, `log_level`) are accepted and ignored rather
1373
+ * than rejected, because this SDK has no SQLite cache backend and no global
1374
+ * logging framework whose level there would be to set. Ignoring them keeps
1375
+ * a shared config object portable between the two SDKs.
1323
1376
  */
1324
1377
  configure(options?: ConfigureOptions): void;
1325
1378
  /**
@@ -1654,4 +1707,4 @@ interface SynapSdkLike {
1654
1707
  };
1655
1708
  }
1656
1709
 
1657
- export { AgentUnavailableError, type AnthropicToolDefinition, AnticipationCacheSnapshot, type AsToolOptions, AuthenticationError, BM25, type BatchCreateResult, COMPACTION_LEVELS, CONTRACT, type CacheNamespace, type CacheStats, type CompactOptions, type CompactionCallback, type CompactionLevel, type ConfigureOptions, ConflictError, ConnectionError, type ContextMetadata, ContextNotFoundError, type ConversationContext, type ConversationNamespace, type CreateFromFileOptions, type CreateMemoryOptions, type CreateMemoryResult, type Credentials, type CreditBalance, type CreditBucket, type CreditEstimate, type CreditLedgerEntry, type CreditLedgerPage, type CreditsNamespace, DEFAULT_BASE_URL, DEFAULT_DOCUMENT_TYPE, DEFAULT_INGEST_MODE, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUTS, DOCUMENT_TYPES, type DocumentType, ENDPOINTS, type Emotion, type EndpointName, type EndpointSpec, type Episode, type EstimateOptions, type Fact, type FetchOptions, type FlatMemory, type FormatOptions, HttpTransport, type HttpTransportOptions, INGEST_MODES, ITEM_COLLECTIONS, type IngestMode, type IngestTranscriptOptions, type InstanceNamespace, InsufficientCreditsError, InvalidConversationIdError, InvalidInputError, InvalidInstanceIdError, type ItemCollection, type Json, type LedgerOptions, type ListenOptions, ListeningAlreadyActiveError, ListeningNotActiveError, type MemoriesInterface, NetworkTimeoutError, type NormalisedContext, type OpenAiToolDefinition, PermanentError, type Preference, RateLimitError, type RawContext, type RawContextItem, type RawConversationContext, type RecordMessageOptions, type RecordThinkingOptions, type RedeemResult, type RequestOptions, type RetryPolicy, SDKError, SDK_VERSION, type Scope, type SendMessageOptions, ServiceUnavailableError, SessionExpiredError, SynapClient, type SynapClientOptions, SynapError, type SynapErrorCode, type SynapErrorOptions, SynapPermanentError, type SynapSdkLike, SynapTransientError, THRESHOLDS, type TemporalEvent, type TemporalFields, type TimeoutConfig, type ToolDefinition, type ToolHandler, type ToolScope, type ToolStyle, TranscriptConflictError, type TranscriptIngestResult, type TranscriptTurn, TransientError, type UnifiedContext, type UnifiedFetchOptions, type UpdateMemoryOptions, type UserNamespace, type WaitOptions, type WriteInvalidator, flattenContextItems, formatForPrompt, isOutcomeUnknown, isRecallQuery, isSynapError, mergeScopeResults, resolvePath, shouldRetry, stem, tokenize };
1710
+ export { AgentUnavailableError, type AnthropicToolDefinition, AnticipationCacheSnapshot, type AsToolOptions, AuthenticationError, BM25, type BatchCreateResult, COMPACTION_LEVELS, CONTRACT, type CacheNamespace, type CacheStats, type CompactOptions, type CompactionCallback, type CompactionLevel, type ConfigureOptions, ConflictError, ConnectionError, type ContextMetadata, ContextNotFoundError, type ConversationContext, type ConversationNamespace, type CreateFromFileOptions, type CreateMemoryOptions, type CreateMemoryResult, type Credentials, type CreditBalance, type CreditBucket, type CreditEstimate, type CreditLedgerEntry, type CreditLedgerPage, type CreditsNamespace, DEFAULT_BASE_URL, DEFAULT_DOCUMENT_TYPE, DEFAULT_INGEST_MODE, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUTS, DOCUMENT_TYPES, type DocumentType, ENDPOINTS, type Emotion, type EndpointName, type EndpointSpec, type Episode, type EstimateOptions, type Fact, type FetchOptions, type FlatMemory, type FormatOptions, HttpTransport, type HttpTransportOptions, INGEST_MODES, ITEM_COLLECTIONS, type IngestMode, type IngestTranscriptOptions, type InstanceNamespace, InsufficientCreditsError, InvalidConversationIdError, InvalidInputError, InvalidInstanceIdError, type ItemCollection, type Json, type LedgerOptions, type ListenOptions, ListeningAlreadyActiveError, ListeningNotActiveError, type MemoriesInterface, NetworkTimeoutError, type NormalisedContext, type OpenAiToolDefinition, PermanentError, type Preference, RateLimitError, type RawContext, type RawContextItem, type RawConversationContext, type RecordMessageOptions, type RecordThinkingOptions, type RedeemResult, type RequestOptions, type RetryPolicy, SDKError, SDK_VERSION, type Scope, type SendMessageOptions, ServiceUnavailableError, SessionExpiredError, SynapClient, type SynapClientOptions, SynapError, type SynapErrorCode, type SynapErrorOptions, type SynapLogLevel, type SynapLogger, SynapPermanentError, type SynapSdkLike, SynapTransientError, THRESHOLDS, type TemporalEvent, type TemporalFields, type TimeoutConfig, type ToolDefinition, type ToolHandler, type ToolScope, type ToolStyle, TranscriptConflictError, type TranscriptIngestResult, type TranscriptTurn, TransientError, type UnifiedContext, type UnifiedFetchOptions, type UpdateMemoryOptions, type UserNamespace, type WaitOptions, type WriteInvalidator, flattenContextItems, formatForPrompt, isOutcomeUnknown, isRecallQuery, isSynapError, mergeScopeResults, resolvePath, shouldRetry, stem, tokenize };
package/dist/index.js CHANGED
@@ -212,7 +212,12 @@ function checkCustomerId(isolation, customerId, where, instanceId) {
212
212
 
213
213
  // src/transport/http.ts
214
214
  var DEFAULT_BASE_URL = "https://synap-cloud-prod.maximem.ai";
215
- var DEFAULT_TIMEOUTS = { connect: 5, read: 30, write: 10 };
215
+ var DEFAULT_TIMEOUTS = {
216
+ connect: 5,
217
+ read: 30,
218
+ write: 10,
219
+ streamIdle: 60
220
+ };
216
221
  var HttpTransport = class {
217
222
  baseUrl;
218
223
  // Mutable so `configure()` can adjust them before the first request, and so
@@ -1121,7 +1126,7 @@ var CompactionSubscribers = class {
1121
1126
  function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
1122
1127
  }, onTurn = () => {
1123
1128
  }, onFetched = () => {
1124
- }) {
1129
+ }, localShortTerm = { prompt: () => null, compacted: () => null }) {
1125
1130
  const convId = (v) => {
1126
1131
  const id = typeof v === "string" ? v : v.conversation_id ?? v.conversationId;
1127
1132
  if (!id) throw new InvalidInputError("conversation_id is required");
@@ -1202,6 +1207,8 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1202
1207
  },
1203
1208
  get_context_for_prompt: async (options = {}) => {
1204
1209
  const id = convId(options);
1210
+ const local = localShortTerm.prompt(id, options.style ?? "structured");
1211
+ if (local !== null) return local;
1205
1212
  const result = await transport.request("context_for_prompt", {
1206
1213
  pathParams: { conversation_id: id },
1207
1214
  query: { style: options.style ?? "structured" }
@@ -1213,6 +1220,10 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
1213
1220
  },
1214
1221
  get_compacted: async (options) => {
1215
1222
  const id = convId(options);
1223
+ if (options.version === void 0) {
1224
+ const local = localShortTerm.compacted(id, options.format ?? "structured");
1225
+ if (local !== null) return local;
1226
+ }
1216
1227
  const query = { format: options.format ?? "structured" };
1217
1228
  if (options.version !== void 0) query["version"] = options.version;
1218
1229
  try {
@@ -1343,6 +1354,18 @@ function envTls() {
1343
1354
  if (raw === void 0 || raw === "") return void 0;
1344
1355
  return !["0", "false", "no", "off"].includes(raw.trim().toLowerCase());
1345
1356
  }
1357
+ function thinkingMetadata(options) {
1358
+ const md = { ...options.metadata ?? {} };
1359
+ const step = options.step_index ?? options.stepIndex;
1360
+ if (step !== void 0 && md["step_index"] === void 0) {
1361
+ md["step_index"] = String(step);
1362
+ }
1363
+ const kind = options.thought_type ?? options.thoughtType;
1364
+ if (kind && md["thought_type"] === void 0) {
1365
+ md["thought_type"] = kind;
1366
+ }
1367
+ return md;
1368
+ }
1346
1369
  function createInstanceNamespace(credentials, cache, onTurn) {
1347
1370
  let client = null;
1348
1371
  let dispatchCompaction = null;
@@ -1454,7 +1477,7 @@ function createInstanceNamespace(credentials, cache, onTurn) {
1454
1477
  user_id: options.user_id ?? options.userId ?? "",
1455
1478
  customer_id: options.customer_id ?? options.customerId ?? "",
1456
1479
  session_id: options.session_id ?? options.sessionId ?? "",
1457
- metadata: options.metadata ?? {},
1480
+ metadata: thinkingMetadata(options),
1458
1481
  timestamp_ms: Date.now()
1459
1482
  });
1460
1483
  },
@@ -1637,11 +1660,11 @@ var ShortTermStore = class {
1637
1660
  // src/context/overlay.ts
1638
1661
  var MAX_TURNS = 20;
1639
1662
  var TOKEN_BUDGET = 2e3;
1640
- function verbatimOverlayEnabled() {
1663
+ function verbatimOverlayEnabled(configured) {
1641
1664
  const raw = (getEnv("SYNAP_ST_VERBATIM_OVERLAY") ?? "").trim().toLowerCase();
1642
1665
  if (["0", "false", "no", "off"].includes(raw)) return false;
1643
1666
  if (["1", "true", "yes", "on"].includes(raw)) return true;
1644
- return true;
1667
+ return configured ?? true;
1645
1668
  }
1646
1669
  function estimateTokens(turn) {
1647
1670
  return Math.max(1, Math.floor(String(turn.content ?? "").length / 4));
@@ -1655,13 +1678,13 @@ function budgetRecentTurns(turns) {
1655
1678
  }
1656
1679
  return windowed;
1657
1680
  }
1658
- function overlayLocalRecentTurns(response, store, conversationId) {
1681
+ function overlayLocalRecentTurns(response, store, conversationId, configured) {
1659
1682
  if (conversationId === void 0 || conversationId === "") return;
1660
1683
  try {
1661
1684
  const entry = store.get(conversationId);
1662
1685
  if (entry === null) return;
1663
1686
  const existing = response.conversation_context ?? null;
1664
- if (existing !== null && !verbatimOverlayEnabled()) return;
1687
+ if (existing !== null && !verbatimOverlayEnabled(configured)) return;
1665
1688
  const localTurns = budgetRecentTurns(entry.recentTurns);
1666
1689
  const serverField = (name) => existing === null ? void 0 : existing[name];
1667
1690
  const orLocal = (name, local) => {
@@ -1683,6 +1706,158 @@ function overlayLocalRecentTurns(response, store, conversationId) {
1683
1706
  }
1684
1707
  }
1685
1708
 
1709
+ // src/context/local-prompt.ts
1710
+ var SUPPORTED_STYLES = ["structured", "narrative", "bullet_points"];
1711
+ var DEFAULT_STYLE = "structured";
1712
+ function stringify(value) {
1713
+ if (value === null || value === void 0) return "";
1714
+ const t = typeof value;
1715
+ if (t === "string" || t === "number" || t === "boolean") return String(value);
1716
+ try {
1717
+ return JSON.stringify(value) ?? "";
1718
+ } catch {
1719
+ return String(value);
1720
+ }
1721
+ }
1722
+ function asListOfObjects(value) {
1723
+ if (!value) return [];
1724
+ if (Array.isArray(value)) {
1725
+ const out = [];
1726
+ for (const v of value) {
1727
+ if (v && typeof v === "object" && !Array.isArray(v)) out.push(v);
1728
+ else if (typeof v === "string") out.push({ content: v });
1729
+ }
1730
+ return out;
1731
+ }
1732
+ if (typeof value === "object") return [value];
1733
+ return [];
1734
+ }
1735
+ function itemText(item) {
1736
+ return item["content"] || item["text"] || stringify(item);
1737
+ }
1738
+ function buildRecentMessages(turns) {
1739
+ return (turns ?? []).map((t, idx) => ({
1740
+ role: t.role || "user",
1741
+ content: t.content ?? "",
1742
+ timestamp: t.timestamp,
1743
+ message_id: `local-${idx}`
1744
+ }));
1745
+ }
1746
+ function compactedMessageCount(_entry) {
1747
+ return 0;
1748
+ }
1749
+ function formatStructured(entry, recent) {
1750
+ const parts = [];
1751
+ if (entry.summary) {
1752
+ parts.push("## Summary");
1753
+ parts.push(entry.summary.trim());
1754
+ }
1755
+ if (entry.factualParagraph) {
1756
+ parts.push("## Facts");
1757
+ parts.push(entry.factualParagraph.trim());
1758
+ }
1759
+ if (entry.conversationalParagraph) {
1760
+ parts.push("## Conversation");
1761
+ parts.push(entry.conversationalParagraph.trim());
1762
+ }
1763
+ const cs = entry.currentState ?? {};
1764
+ if (Object.keys(cs).length > 0) {
1765
+ parts.push("## Current State");
1766
+ for (const [k, v] of Object.entries(cs)) parts.push(`- ${k}: ${stringify(v)}`);
1767
+ }
1768
+ const ke = entry.keyExtractions ?? {};
1769
+ if (Object.keys(ke).length > 0) {
1770
+ parts.push("## Key Extractions");
1771
+ for (const [cat, items] of Object.entries(ke)) {
1772
+ if (!items) continue;
1773
+ parts.push(`### ${cat}`);
1774
+ for (const it of asListOfObjects(items)) parts.push(`- ${itemText(it)}`);
1775
+ }
1776
+ }
1777
+ if (recent.length > 0) {
1778
+ parts.push(`## Recent Turns (${recent.length})`);
1779
+ for (const m of recent) parts.push(`**${m.role}**: ${m.content}`);
1780
+ }
1781
+ return parts.filter(Boolean).join("\n\n").trim();
1782
+ }
1783
+ function formatNarrative(entry, recent) {
1784
+ const parts = [];
1785
+ if (entry.summary) parts.push(entry.summary.trim());
1786
+ if (entry.conversationalParagraph) parts.push(entry.conversationalParagraph.trim());
1787
+ else if (entry.factualParagraph) parts.push(entry.factualParagraph.trim());
1788
+ if (recent.length > 0) {
1789
+ const tail = recent.map((m) => `${m.role}: ${m.content}`).join("\n");
1790
+ parts.push(`Recent exchanges:
1791
+ ${tail}`);
1792
+ }
1793
+ return parts.filter(Boolean).join("\n\n").trim();
1794
+ }
1795
+ function formatBullets(entry, recent) {
1796
+ const parts = [];
1797
+ if (entry.summary) parts.push(`- Summary: ${entry.summary.trim()}`);
1798
+ for (const [k, v] of Object.entries(entry.currentState ?? {})) {
1799
+ parts.push(`- ${k}: ${stringify(v)}`);
1800
+ }
1801
+ for (const [cat, items] of Object.entries(entry.keyExtractions ?? {})) {
1802
+ for (const it of asListOfObjects(items)) parts.push(`- (${cat}) ${itemText(it)}`);
1803
+ }
1804
+ for (const m of recent) parts.push(`- ${m.role}: ${m.content}`);
1805
+ return parts.join("\n").trim();
1806
+ }
1807
+ function formatBlock(entry, recent, style) {
1808
+ if (style === "narrative") return formatNarrative(entry, recent);
1809
+ if (style === "bullet_points") return formatBullets(entry, recent);
1810
+ return formatStructured(entry, recent);
1811
+ }
1812
+ function renderForPrompt(entry, style = DEFAULT_STYLE, now = Date.now) {
1813
+ const chosen = SUPPORTED_STYLES.includes(style) ? style : DEFAULT_STYLE;
1814
+ const recent = buildRecentMessages(entry.recentTurns);
1815
+ const formatted = formatBlock(entry, recent, chosen);
1816
+ const available = entry.compactionId !== null || entry.recentTurns.length > 0;
1817
+ let compactionAge = null;
1818
+ if (entry.compactedAt !== null) {
1819
+ const parsed = Date.parse(entry.compactedAt);
1820
+ if (!Number.isNaN(parsed)) compactionAge = Math.floor((now() - parsed) / 1e3);
1821
+ }
1822
+ const compacted = compactedMessageCount(entry);
1823
+ return {
1824
+ formatted_context: available ? formatted : null,
1825
+ available,
1826
+ is_stale: false,
1827
+ compression_ratio: null,
1828
+ validation_score: null,
1829
+ compaction_age_seconds: compactionAge,
1830
+ quality_warning: false,
1831
+ recent_messages: recent,
1832
+ recent_message_count: recent.length,
1833
+ compacted_message_count: compacted,
1834
+ total_message_count: recent.length + compacted
1835
+ };
1836
+ }
1837
+ function renderCompacted(entry, format = "structured", correlationId = "") {
1838
+ if (entry.compactionId === null) return null;
1839
+ const formatted = formatBlock(
1840
+ entry,
1841
+ buildRecentMessages(entry.recentTurns),
1842
+ format === "narrative" ? "narrative" : "structured"
1843
+ );
1844
+ return {
1845
+ compacted_context: formatted,
1846
+ compaction_id: entry.compactionId,
1847
+ compacted_at: entry.compactedAt,
1848
+ conversation_id: entry.conversationId,
1849
+ current_state: entry.currentState,
1850
+ correlation_id: correlationId,
1851
+ original_token_count: null,
1852
+ compacted_token_count: null,
1853
+ compression_ratio: null,
1854
+ level_applied: null,
1855
+ validation_score: null,
1856
+ validation_passed: null,
1857
+ quality_warning: false
1858
+ };
1859
+ }
1860
+
1686
1861
  // src/context/user-summary.ts
1687
1862
  var USER_SUMMARY_INTERVAL = 5;
1688
1863
  var MAX_SUMMARY_ITEMS = 3;
@@ -2271,6 +2446,9 @@ var SynapClient = class {
2271
2446
  #registryKeys;
2272
2447
  #compactionSubscribers;
2273
2448
  #shortTerm;
2449
+ #stVerbatimOverlay;
2450
+ #logger;
2451
+ #stAuthoritative;
2274
2452
  #turns;
2275
2453
  #fetchHooks;
2276
2454
  constructor(options = {}) {
@@ -2341,6 +2519,9 @@ var SynapClient = class {
2341
2519
  this.client = { context: { fetch: async (o2 = {}) => fetchContext(this.#transport, "client", o2, this.#fetchHooks) } };
2342
2520
  this.#compactionSubscribers = new CompactionSubscribers();
2343
2521
  this.#shortTerm = new ShortTermStore();
2522
+ this.#stVerbatimOverlay = options.st_verbatim_overlay ?? options.stVerbatimOverlay;
2523
+ this.#logger = options.logger;
2524
+ this.#stAuthoritative = options.sdk_st_authoritative ?? options.sdkStAuthoritative;
2344
2525
  this.#turns = new TurnCounter();
2345
2526
  this.conversation = createConversationNamespace(
2346
2527
  this.#transport,
@@ -2361,8 +2542,12 @@ var SynapClient = class {
2361
2542
  this.#shortTerm.appendTurn(conversationId, role, content, timestamp);
2362
2543
  },
2363
2544
  (response, conversationId, userId) => {
2364
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2545
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2365
2546
  this.#injectUserSummary(response, conversationId, userId);
2547
+ },
2548
+ {
2549
+ prompt: (conversationId, style) => this.#localPrompt(conversationId, style),
2550
+ compacted: (conversationId, format) => this.#localCompacted(conversationId, format)
2366
2551
  }
2367
2552
  );
2368
2553
  this.memories = createMemoriesInterface(this.#transport);
@@ -2521,11 +2706,12 @@ var SynapClient = class {
2521
2706
  * initialization: the transport and cache are already built by then and a
2522
2707
  * half-applied config is worse than a rejected one.
2523
2708
  *
2524
- * The Python-only keys (`storage_path`, `cache_backend`,
2525
- * `session_timeout_minutes`, `logger`) are accepted and ignored rather than
2526
- * rejected, because this SDK has no SQLite cache backend and no global
2527
- * logger to swap. Ignoring them keeps a shared config object portable
2528
- * between the two SDKs.
2709
+ * `logger` is honoured here as well as on the constructor. The remaining
2710
+ * Python-only keys (`storage_path`, `cache_backend`,
2711
+ * `session_timeout_minutes`, `log_level`) are accepted and ignored rather
2712
+ * than rejected, because this SDK has no SQLite cache backend and no global
2713
+ * logging framework whose level there would be to set. Ignoring them keeps
2714
+ * a shared config object portable between the two SDKs.
2529
2715
  */
2530
2716
  configure(options = {}) {
2531
2717
  if (this.#initialized) {
@@ -2535,6 +2721,7 @@ var SynapClient = class {
2535
2721
  if (options.retryPolicy !== void 0 || options.retry_policy !== void 0) {
2536
2722
  this.#transport.setRetryPolicy(options.retryPolicy ?? options.retry_policy ?? null);
2537
2723
  }
2724
+ if (options.logger !== void 0) this.#logger = options.logger;
2538
2725
  }
2539
2726
  /**
2540
2727
  * Fetch and merge context across every scope with an identifier, in one call.
@@ -2632,7 +2819,7 @@ var SynapClient = class {
2632
2819
  }
2633
2820
  const merged = mergeScopeResults(successful);
2634
2821
  for (const [, response] of successful) {
2635
- overlayLocalRecentTurns(response, this.#shortTerm, conversationId);
2822
+ overlayLocalRecentTurns(response, this.#shortTerm, conversationId, this.#stVerbatimOverlay);
2636
2823
  }
2637
2824
  if (includeConversationContext && conversationId !== void 0 && conversationId !== "") {
2638
2825
  try {
@@ -2729,8 +2916,44 @@ var SynapClient = class {
2729
2916
  if (summary === null) return;
2730
2917
  mergeUserSummary(response, summary);
2731
2918
  }
2732
- /** Python logs via `logger.warning`; here it is one overridable seam. */
2919
+ /**
2920
+ * Whether the SDK is authoritative for short-term context.
2921
+ *
2922
+ * Resolution order matches Python's `_is_st_authoritative`: the explicit
2923
+ * option first, then `SYNAP_SDK_ST_AUTHORITATIVE` (truthy strings only).
2924
+ * Defaults to false, so nothing changes for a caller who never sets it.
2925
+ */
2926
+ #isStAuthoritative() {
2927
+ if (this.#stAuthoritative === true) return true;
2928
+ const raw = (getEnv("SYNAP_SDK_ST_AUTHORITATIVE") ?? "").trim().toLowerCase();
2929
+ return ["1", "true", "yes", "on"].includes(raw);
2930
+ }
2931
+ /**
2932
+ * Render `get_context_for_prompt` from the local store, or null to fall
2933
+ * through to the network. Warm means a compaction landed OR raw turns are
2934
+ * buffered: with neither there is nothing to render and the server's answer
2935
+ * is the only real one.
2936
+ */
2937
+ #localPrompt(conversationId, style) {
2938
+ if (!this.#isStAuthoritative()) return null;
2939
+ const entry = this.#shortTerm.get(conversationId);
2940
+ if (entry === null) return null;
2941
+ if (entry.compactionId === null && entry.recentTurns.length === 0) return null;
2942
+ return renderForPrompt(entry, style);
2943
+ }
2944
+ /** Same, for `get_compacted`. Requires an actual compaction, not just turns. */
2945
+ #localCompacted(conversationId, format) {
2946
+ if (!this.#isStAuthoritative()) return null;
2947
+ const entry = this.#shortTerm.get(conversationId);
2948
+ if (entry === null || entry.compactionId === null) return null;
2949
+ return renderCompacted(entry, format);
2950
+ }
2951
+ /** Every diagnostic the SDK emits passes through here. */
2733
2952
  #warn(message) {
2953
+ if (this.#logger !== void 0) {
2954
+ this.#logger("warn", message);
2955
+ return;
2956
+ }
2734
2957
  console.warn(`[synap] ${message}`);
2735
2958
  }
2736
2959
  /** Release timers and connections. Safe to call more than once. */
@@ -2751,7 +2974,8 @@ function pick3(o2, snake, camel) {
2751
2974
  return a !== void 0 ? a : o2[camel];
2752
2975
  }
2753
2976
  function resolveBaseUrl(options) {
2754
- if (options.baseUrl !== void 0) return options.baseUrl;
2977
+ const explicit = options.baseUrl ?? options.api_base_url;
2978
+ if (explicit !== void 0) return explicit;
2755
2979
  const fromEnv = getEnv("SYNAP_BASE_URL")?.trim();
2756
2980
  return fromEnv !== void 0 && fromEnv !== "" ? fromEnv : void 0;
2757
2981
  }