@maximem/synap-js-sdk 0.5.11 → 0.5.13
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.cjs +137 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +98 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -949,6 +949,16 @@ interface WaitOptions {
|
|
|
949
949
|
signal?: AbortSignal;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Scope-aware context fetching.
|
|
954
|
+
*
|
|
955
|
+
* Scopes are client > customer > user. `conversation_id` is NOT a tier: it
|
|
956
|
+
* groups turns within a scope. Passing it narrows an existing scope rather
|
|
957
|
+
* than selecting a different one.
|
|
958
|
+
*/
|
|
959
|
+
|
|
960
|
+
type Scope = 'user' | 'customer' | 'client' | 'conversation';
|
|
961
|
+
|
|
952
962
|
/**
|
|
953
963
|
* Conversation namespace. Mirrors `ConversationInterface` and
|
|
954
964
|
* `ConversationContextInterface` in the Python SDK.
|
|
@@ -1106,16 +1116,6 @@ type WriteInvalidator = (ids: {
|
|
|
1106
1116
|
customerId?: string | undefined;
|
|
1107
1117
|
}) => void;
|
|
1108
1118
|
|
|
1109
|
-
/**
|
|
1110
|
-
* Scope-aware context fetching.
|
|
1111
|
-
*
|
|
1112
|
-
* Scopes are client > customer > user. `conversation_id` is NOT a tier: it
|
|
1113
|
-
* groups turns within a scope. Passing it narrows an existing scope rather
|
|
1114
|
-
* than selecting a different one.
|
|
1115
|
-
*/
|
|
1116
|
-
|
|
1117
|
-
type Scope = 'user' | 'customer' | 'client' | 'conversation';
|
|
1118
|
-
|
|
1119
1119
|
/** User namespace. Mirrors `UserInterface` in the Python SDK. */
|
|
1120
1120
|
|
|
1121
1121
|
interface UserNamespace {
|
|
@@ -1873,7 +1873,7 @@ declare class SynapClient {
|
|
|
1873
1873
|
* build when the two disagree, which is the only thing that makes a manual
|
|
1874
1874
|
* publish safe.
|
|
1875
1875
|
*/
|
|
1876
|
-
declare const SDK_VERSION = "0.5.
|
|
1876
|
+
declare const SDK_VERSION = "0.5.13";
|
|
1877
1877
|
|
|
1878
1878
|
/**
|
|
1879
1879
|
* `response_shape: "ranked"`: one list in the order the server ranked it.
|
package/dist/index.d.ts
CHANGED
|
@@ -949,6 +949,16 @@ interface WaitOptions {
|
|
|
949
949
|
signal?: AbortSignal;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Scope-aware context fetching.
|
|
954
|
+
*
|
|
955
|
+
* Scopes are client > customer > user. `conversation_id` is NOT a tier: it
|
|
956
|
+
* groups turns within a scope. Passing it narrows an existing scope rather
|
|
957
|
+
* than selecting a different one.
|
|
958
|
+
*/
|
|
959
|
+
|
|
960
|
+
type Scope = 'user' | 'customer' | 'client' | 'conversation';
|
|
961
|
+
|
|
952
962
|
/**
|
|
953
963
|
* Conversation namespace. Mirrors `ConversationInterface` and
|
|
954
964
|
* `ConversationContextInterface` in the Python SDK.
|
|
@@ -1106,16 +1116,6 @@ type WriteInvalidator = (ids: {
|
|
|
1106
1116
|
customerId?: string | undefined;
|
|
1107
1117
|
}) => void;
|
|
1108
1118
|
|
|
1109
|
-
/**
|
|
1110
|
-
* Scope-aware context fetching.
|
|
1111
|
-
*
|
|
1112
|
-
* Scopes are client > customer > user. `conversation_id` is NOT a tier: it
|
|
1113
|
-
* groups turns within a scope. Passing it narrows an existing scope rather
|
|
1114
|
-
* than selecting a different one.
|
|
1115
|
-
*/
|
|
1116
|
-
|
|
1117
|
-
type Scope = 'user' | 'customer' | 'client' | 'conversation';
|
|
1118
|
-
|
|
1119
1119
|
/** User namespace. Mirrors `UserInterface` in the Python SDK. */
|
|
1120
1120
|
|
|
1121
1121
|
interface UserNamespace {
|
|
@@ -1873,7 +1873,7 @@ declare class SynapClient {
|
|
|
1873
1873
|
* build when the two disagree, which is the only thing that makes a manual
|
|
1874
1874
|
* publish safe.
|
|
1875
1875
|
*/
|
|
1876
|
-
declare const SDK_VERSION = "0.5.
|
|
1876
|
+
declare const SDK_VERSION = "0.5.13";
|
|
1877
1877
|
|
|
1878
1878
|
/**
|
|
1879
1879
|
* `response_shape: "ranked"`: one list in the order the server ranked it.
|
package/dist/index.js
CHANGED
|
@@ -408,13 +408,25 @@ function isOutcomeUnknown(error) {
|
|
|
408
408
|
if (error instanceof ConflictError) return false;
|
|
409
409
|
if (error instanceof InsufficientCreditsError) return false;
|
|
410
410
|
if (error instanceof ContextNotFoundError) return false;
|
|
411
|
-
if (error instanceof NetworkTimeoutError) return true;
|
|
412
411
|
if (isConnectionRefusal(error)) return false;
|
|
412
|
+
if (error instanceof NetworkTimeoutError) return true;
|
|
413
413
|
return true;
|
|
414
414
|
}
|
|
415
|
+
var CONNECTION_REFUSAL_CODES = /* @__PURE__ */ new Set([
|
|
416
|
+
"ECONNREFUSED",
|
|
417
|
+
"ENOTFOUND",
|
|
418
|
+
"EAI_AGAIN",
|
|
419
|
+
"ECONNRESET",
|
|
420
|
+
"EHOSTUNREACH"
|
|
421
|
+
]);
|
|
415
422
|
function isConnectionRefusal(error) {
|
|
416
|
-
|
|
417
|
-
|
|
423
|
+
let cur = error;
|
|
424
|
+
for (let depth = 0; depth < 4 && cur !== null && typeof cur === "object"; depth += 1) {
|
|
425
|
+
const code = cur.code;
|
|
426
|
+
if (typeof code === "string" && CONNECTION_REFUSAL_CODES.has(code)) return true;
|
|
427
|
+
cur = cur.cause;
|
|
428
|
+
}
|
|
429
|
+
return false;
|
|
418
430
|
}
|
|
419
431
|
function toTransportError(error, correlationId) {
|
|
420
432
|
if (error instanceof SynapError) return error;
|
|
@@ -422,6 +434,12 @@ function toTransportError(error, correlationId) {
|
|
|
422
434
|
if (name === "AbortError" || name === "TimeoutError") {
|
|
423
435
|
return new NetworkTimeoutError("Request timed out", { correlationId, cause: error });
|
|
424
436
|
}
|
|
437
|
+
if (isConnectionRefusal(error)) {
|
|
438
|
+
return new NetworkTimeoutError(
|
|
439
|
+
`Connection failed: ${error?.message ?? String(error)}`,
|
|
440
|
+
{ correlationId, cause: error }
|
|
441
|
+
);
|
|
442
|
+
}
|
|
425
443
|
return new ServiceUnavailableError(
|
|
426
444
|
`Network request failed: ${error?.message ?? String(error)}`,
|
|
427
445
|
{ correlationId, cause: error }
|
|
@@ -639,6 +657,7 @@ function buildFetchRequest(scope, options) {
|
|
|
639
657
|
const perQuery = opt(options, "max_results_per_query", "maxResultsPerQuery");
|
|
640
658
|
const responseShape = responseShapeOf(options);
|
|
641
659
|
const { mode, precisionLevel } = retrievalOptionsOf(options);
|
|
660
|
+
const { contextMode, lastN } = scopeOptionsOf(scope, options);
|
|
642
661
|
if (searchQuery !== void 0 && !Array.isArray(searchQuery)) {
|
|
643
662
|
throw new InvalidInputError("search_query must be an array when provided");
|
|
644
663
|
}
|
|
@@ -666,21 +685,7 @@ function buildFetchRequest(scope, options) {
|
|
|
666
685
|
switch (scope) {
|
|
667
686
|
case "user":
|
|
668
687
|
{
|
|
669
|
-
if (!userId) throw new InvalidInputError("user_id is required");
|
|
670
688
|
body["user_id"] = userId;
|
|
671
|
-
const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
|
|
672
|
-
if (!VALID_CONTEXT_MODES.includes(contextMode)) {
|
|
673
|
-
throw new InvalidInputError(
|
|
674
|
-
`Invalid context_mode '${contextMode}'. Must be one of: ${VALID_CONTEXT_MODES.join(", ")}`
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
const lastN = opt(options, "last_n_conversations", "lastNConversations") ?? 1;
|
|
678
|
-
if (!Number.isInteger(lastN)) {
|
|
679
|
-
throw new InvalidInputError("last_n_conversations must be an integer");
|
|
680
|
-
}
|
|
681
|
-
if (lastN < 0 || lastN > 20) {
|
|
682
|
-
throw new InvalidInputError("last_n_conversations must be between 0 and 20");
|
|
683
|
-
}
|
|
684
689
|
if (contextMode === "conversation-summary") {
|
|
685
690
|
body["context_mode"] = contextMode;
|
|
686
691
|
body["include_profile"] = opt(options, "include_profile", "includeProfile") ?? true;
|
|
@@ -691,11 +696,9 @@ function buildFetchRequest(scope, options) {
|
|
|
691
696
|
}
|
|
692
697
|
break;
|
|
693
698
|
case "customer":
|
|
694
|
-
if (!customerId) throw new InvalidInputError("customer_id is required");
|
|
695
699
|
body["customer_id"] = customerId;
|
|
696
700
|
break;
|
|
697
701
|
case "conversation":
|
|
698
|
-
if (!conversationId) throw new InvalidInputError("conversation_id is required");
|
|
699
702
|
if (userId !== void 0) body["user_id"] = userId;
|
|
700
703
|
if (customerId !== void 0) body["customer_id"] = customerId;
|
|
701
704
|
break;
|
|
@@ -714,6 +717,33 @@ function responseShapeOf(options) {
|
|
|
714
717
|
}
|
|
715
718
|
return shape;
|
|
716
719
|
}
|
|
720
|
+
var REQUIRED_ID = {
|
|
721
|
+
user: ["user_id", "userId"],
|
|
722
|
+
customer: ["customer_id", "customerId"],
|
|
723
|
+
conversation: ["conversation_id", "conversationId"],
|
|
724
|
+
client: null
|
|
725
|
+
};
|
|
726
|
+
function scopeOptionsOf(scope, options) {
|
|
727
|
+
const required = REQUIRED_ID[scope];
|
|
728
|
+
if (required !== null && !opt(options, required[0], required[1])) {
|
|
729
|
+
throw new InvalidInputError(`${required[0]} is required`);
|
|
730
|
+
}
|
|
731
|
+
const contextMode = opt(options, "context_mode", "contextMode") ?? "in-conversation";
|
|
732
|
+
const lastN = opt(options, "last_n_conversations", "lastNConversations") ?? 1;
|
|
733
|
+
if (scope !== "user") return { contextMode, lastN };
|
|
734
|
+
if (!VALID_CONTEXT_MODES.includes(contextMode)) {
|
|
735
|
+
throw new InvalidInputError(
|
|
736
|
+
`Invalid context_mode '${contextMode}'. Must be one of: ${VALID_CONTEXT_MODES.join(", ")}`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (!Number.isInteger(lastN)) {
|
|
740
|
+
throw new InvalidInputError("last_n_conversations must be an integer");
|
|
741
|
+
}
|
|
742
|
+
if (lastN < 0 || lastN > 20) {
|
|
743
|
+
throw new InvalidInputError("last_n_conversations must be between 0 and 20");
|
|
744
|
+
}
|
|
745
|
+
return { contextMode, lastN };
|
|
746
|
+
}
|
|
717
747
|
var VALID_MODES = ["fast", "accurate"];
|
|
718
748
|
var VALID_PRECISION = ["high", "medium"];
|
|
719
749
|
function retrievalOptionsOf(options) {
|
|
@@ -740,12 +770,23 @@ async function fetchContext(transport, scope, options = {}, hooks = {}) {
|
|
|
740
770
|
);
|
|
741
771
|
const responseShape = responseShapeOf(options);
|
|
742
772
|
retrievalOptionsOf(options);
|
|
773
|
+
const { contextMode } = scopeOptionsOf(scope, options);
|
|
774
|
+
const summaryMode = scope === "user" && contextMode === "conversation-summary";
|
|
775
|
+
const finish = (response2) => {
|
|
776
|
+
hooks.onResponse?.(scope, options, response2);
|
|
777
|
+
return response2;
|
|
778
|
+
};
|
|
779
|
+
const conversationId = opt(options, "conversation_id", "conversationId");
|
|
780
|
+
const skipServerSt = conversationId ? hooks.skipServerShortTerm?.(conversationId) === true : false;
|
|
743
781
|
const network = async () => {
|
|
744
|
-
const { endpoint, body } = buildFetchRequest(
|
|
782
|
+
const { endpoint, body } = buildFetchRequest(
|
|
783
|
+
scope,
|
|
784
|
+
skipServerSt ? { ...options, include_conversation_context: false } : options
|
|
785
|
+
);
|
|
745
786
|
const result = await transport.request(endpoint, { body });
|
|
746
787
|
return applyResponseShape(result?.["context"] ?? {}, responseShape);
|
|
747
788
|
};
|
|
748
|
-
const anticipated = await hooks.beforeFetch?.(scope, options) ?? null;
|
|
789
|
+
const anticipated = summaryMode ? null : await hooks.beforeFetch?.(scope, options) ?? null;
|
|
749
790
|
if (anticipated !== null && anticipated.borderline) {
|
|
750
791
|
const pending = network();
|
|
751
792
|
pending.catch(() => void 0);
|
|
@@ -757,17 +798,17 @@ async function fetchContext(transport, scope, options = {}, hooks = {}) {
|
|
|
757
798
|
if (timer !== void 0) clearTimeout(timer);
|
|
758
799
|
if (won !== null) {
|
|
759
800
|
hooks.onAssembled?.(scope, options, won, startedAt);
|
|
760
|
-
return won;
|
|
801
|
+
return finish(won);
|
|
761
802
|
}
|
|
762
803
|
}
|
|
763
804
|
if (anticipated !== null) {
|
|
764
805
|
hooks.onServedFromCache?.(scope, options, anticipated);
|
|
765
806
|
hooks.onAssembled?.(scope, options, anticipated.response, startedAt);
|
|
766
|
-
return applyResponseShape(anticipated.response, responseShape);
|
|
807
|
+
return finish(applyResponseShape(anticipated.response, responseShape));
|
|
767
808
|
}
|
|
768
809
|
const response = await network();
|
|
769
810
|
hooks.onAssembled?.(scope, options, response, startedAt);
|
|
770
|
-
return response;
|
|
811
|
+
return finish(response);
|
|
771
812
|
}
|
|
772
813
|
|
|
773
814
|
// src/context/normalize.ts
|
|
@@ -1402,7 +1443,7 @@ function storedBatchRows(bodies, result) {
|
|
|
1402
1443
|
function createConversationNamespace(transport, subscribers = new CompactionSubscribers(), onWrite = () => {
|
|
1403
1444
|
}, onTurn = () => {
|
|
1404
1445
|
}, onFetched = () => {
|
|
1405
|
-
}, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({})) {
|
|
1446
|
+
}, localShortTerm = { prompt: () => null, compacted: () => null }, agentModel = () => ({}), fetchHooks = {}) {
|
|
1406
1447
|
const writesInFlight = /* @__PURE__ */ new Map();
|
|
1407
1448
|
const withTurnModel = (body, options) => {
|
|
1408
1449
|
const metadata = withAgentModel(
|
|
@@ -1509,7 +1550,7 @@ function createConversationNamespace(transport, subscribers = new CompactionSubs
|
|
|
1509
1550
|
async fetch(options = {}) {
|
|
1510
1551
|
const id = options.conversation_id ?? options.conversationId;
|
|
1511
1552
|
validateConversationId(id);
|
|
1512
|
-
const response = await fetchContext(transport, "conversation", options);
|
|
1553
|
+
const response = await fetchContext(transport, "conversation", options, fetchHooks);
|
|
1513
1554
|
onFetched(
|
|
1514
1555
|
response,
|
|
1515
1556
|
id,
|
|
@@ -1958,7 +1999,7 @@ function createInstanceNamespace(credentials, cache, onTurn, agentModel = () =>
|
|
|
1958
1999
|
}
|
|
1959
2000
|
|
|
1960
2001
|
// src/version.ts
|
|
1961
|
-
var SDK_VERSION = "0.5.
|
|
2002
|
+
var SDK_VERSION = "0.5.13";
|
|
1962
2003
|
|
|
1963
2004
|
// src/registry.ts
|
|
1964
2005
|
var instances = /* @__PURE__ */ new Map();
|
|
@@ -3776,6 +3817,29 @@ var SynapClient = class _SynapClient {
|
|
|
3776
3817
|
},
|
|
3777
3818
|
onServedFromCache: (_scope, options2, served) => {
|
|
3778
3819
|
this.#emitContextUsed(options2, _scope, served);
|
|
3820
|
+
},
|
|
3821
|
+
// The periodic user-summary splice, as Python runs it at the end of the
|
|
3822
|
+
// user, customer and client fetches (the conversation fetch does its own
|
|
3823
|
+
// through `onFetched`). Until 0.5.12 only the conversation fetch had it,
|
|
3824
|
+
// so the same usage gave the model different context in JS.
|
|
3825
|
+
// Python's `_should_skip_server_st`: when this process already holds the
|
|
3826
|
+
// conversation's compaction and every turn after it, the server is told
|
|
3827
|
+
// not to assemble them again.
|
|
3828
|
+
skipServerShortTerm: (conversationId) => {
|
|
3829
|
+
if (!this.#isStAuthoritative()) return false;
|
|
3830
|
+
const entry = this.#shortTerm.get(conversationId);
|
|
3831
|
+
return entry !== null && entry.compactionId !== null && isComplete(entry);
|
|
3832
|
+
},
|
|
3833
|
+
onResponse: (scope, options2, response) => {
|
|
3834
|
+
if (scope === "conversation") return;
|
|
3835
|
+
const o2 = options2;
|
|
3836
|
+
const owner = scope === "user" ? o2["user_id"] ?? o2["userId"] : scope === "customer" ? o2["customer_id"] ?? o2["customerId"] : "_client";
|
|
3837
|
+
this.#injectUserSummary(
|
|
3838
|
+
response,
|
|
3839
|
+
o2["conversation_id"] ?? o2["conversationId"],
|
|
3840
|
+
owner,
|
|
3841
|
+
rungKey(o2["scope_path"] ?? o2["scopePath"])
|
|
3842
|
+
);
|
|
3779
3843
|
}
|
|
3780
3844
|
};
|
|
3781
3845
|
this.user = createUserNamespace(this.#transport, this.#fetchHooks);
|
|
@@ -3829,7 +3893,12 @@ var SynapClient = class _SynapClient {
|
|
|
3829
3893
|
reseed: (conversationId, response) => this.#reseedShortTerm(conversationId, response),
|
|
3830
3894
|
historyExcerpts: (conversationId, response, query) => this.#withHistoryExcerpts(conversationId, response, query)
|
|
3831
3895
|
},
|
|
3832
|
-
() => this.#agentModel
|
|
3896
|
+
() => this.#agentModel,
|
|
3897
|
+
// The same hooks as the other three scopes: the anticipation cache first,
|
|
3898
|
+
// as Python's conversation fetch does, and the audit events. Until 0.5.13
|
|
3899
|
+
// the conversation fetch was built without them, so every JS conversation
|
|
3900
|
+
// fetch was a billed round trip, including ones the stream had answered.
|
|
3901
|
+
this.#fetchHooks
|
|
3833
3902
|
);
|
|
3834
3903
|
this.memories = createMemoriesInterface(this.#transport, ({ userId, customerId }) => {
|
|
3835
3904
|
for (const id of /* @__PURE__ */ new Set([userId, customerId])) {
|
|
@@ -4443,7 +4512,7 @@ function resolveBaseUrl(options) {
|
|
|
4443
4512
|
function resolveCredentials(options) {
|
|
4444
4513
|
const apiKey = options.apiKey ?? getEnv("SYNAP_API_KEY");
|
|
4445
4514
|
if (!apiKey) {
|
|
4446
|
-
throw new
|
|
4515
|
+
throw new AuthenticationError(
|
|
4447
4516
|
"No Synap API key found. Set SYNAP_API_KEY in your environment, or pass apiKey to the SynapClient constructor."
|
|
4448
4517
|
);
|
|
4449
4518
|
}
|