@witqq/agent-sdk 0.7.0 → 0.8.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/README.md +140 -34
- package/dist/{types-CqvUAYxt.d.cts → agent-CW9XbmG_.d.ts} +137 -102
- package/dist/{types-CqvUAYxt.d.ts → agent-DxY68NZL.d.cts} +137 -102
- package/dist/auth/index.cjs +72 -1
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +21 -154
- package/dist/auth/index.d.ts +21 -154
- package/dist/auth/index.js +72 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/backends/claude.cjs +480 -261
- package/dist/backends/claude.cjs.map +1 -1
- package/dist/backends/claude.d.cts +3 -1
- package/dist/backends/claude.d.ts +3 -1
- package/dist/backends/claude.js +480 -261
- package/dist/backends/claude.js.map +1 -1
- package/dist/backends/copilot.cjs +329 -97
- package/dist/backends/copilot.cjs.map +1 -1
- package/dist/backends/copilot.d.cts +12 -4
- package/dist/backends/copilot.d.ts +12 -4
- package/dist/backends/copilot.js +329 -97
- package/dist/backends/copilot.js.map +1 -1
- package/dist/backends/vercel-ai.cjs +294 -61
- package/dist/backends/vercel-ai.cjs.map +1 -1
- package/dist/backends/vercel-ai.d.cts +3 -1
- package/dist/backends/vercel-ai.d.ts +3 -1
- package/dist/backends/vercel-ai.js +294 -61
- package/dist/backends/vercel-ai.js.map +1 -1
- package/dist/backends-BSrsBYFn.d.cts +39 -0
- package/dist/backends-BSrsBYFn.d.ts +39 -0
- package/dist/chat/accumulator.cjs +1 -1
- package/dist/chat/accumulator.cjs.map +1 -1
- package/dist/chat/accumulator.d.cts +5 -2
- package/dist/chat/accumulator.d.ts +5 -2
- package/dist/chat/accumulator.js +1 -1
- package/dist/chat/accumulator.js.map +1 -1
- package/dist/chat/backends.cjs +736 -746
- package/dist/chat/backends.cjs.map +1 -1
- package/dist/chat/backends.d.cts +10 -6
- package/dist/chat/backends.d.ts +10 -6
- package/dist/chat/backends.js +736 -725
- package/dist/chat/backends.js.map +1 -1
- package/dist/chat/context.cjs +50 -0
- package/dist/chat/context.cjs.map +1 -1
- package/dist/chat/context.d.cts +27 -3
- package/dist/chat/context.d.ts +27 -3
- package/dist/chat/context.js +50 -0
- package/dist/chat/context.js.map +1 -1
- package/dist/chat/core.cjs +25 -2
- package/dist/chat/core.cjs.map +1 -1
- package/dist/chat/core.d.cts +30 -381
- package/dist/chat/core.d.ts +30 -381
- package/dist/chat/core.js +24 -3
- package/dist/chat/core.js.map +1 -1
- package/dist/chat/errors.cjs +48 -26
- package/dist/chat/errors.cjs.map +1 -1
- package/dist/chat/errors.d.cts +6 -31
- package/dist/chat/errors.d.ts +6 -31
- package/dist/chat/errors.js +48 -25
- package/dist/chat/errors.js.map +1 -1
- package/dist/chat/events.cjs.map +1 -1
- package/dist/chat/events.d.cts +6 -2
- package/dist/chat/events.d.ts +6 -2
- package/dist/chat/events.js.map +1 -1
- package/dist/chat/index.cjs +1199 -1008
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +35 -10
- package/dist/chat/index.d.ts +35 -10
- package/dist/chat/index.js +1196 -987
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react/theme.css +2517 -0
- package/dist/chat/react.cjs +2003 -1153
- package/dist/chat/react.cjs.map +1 -1
- package/dist/chat/react.d.cts +590 -121
- package/dist/chat/react.d.ts +590 -121
- package/dist/chat/react.js +1984 -1151
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/runtime.cjs +401 -186
- package/dist/chat/runtime.cjs.map +1 -1
- package/dist/chat/runtime.d.cts +92 -28
- package/dist/chat/runtime.d.ts +92 -28
- package/dist/chat/runtime.js +401 -186
- package/dist/chat/runtime.js.map +1 -1
- package/dist/chat/server.cjs +2234 -209
- package/dist/chat/server.cjs.map +1 -1
- package/dist/chat/server.d.cts +451 -90
- package/dist/chat/server.d.ts +451 -90
- package/dist/chat/server.js +2221 -210
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/sessions.cjs +25 -43
- package/dist/chat/sessions.cjs.map +1 -1
- package/dist/chat/sessions.d.cts +37 -118
- package/dist/chat/sessions.d.ts +37 -118
- package/dist/chat/sessions.js +25 -43
- package/dist/chat/sessions.js.map +1 -1
- package/dist/chat/sqlite.cjs +441 -0
- package/dist/chat/sqlite.cjs.map +1 -0
- package/dist/chat/sqlite.d.cts +128 -0
- package/dist/chat/sqlite.d.ts +128 -0
- package/dist/chat/sqlite.js +435 -0
- package/dist/chat/sqlite.js.map +1 -0
- package/dist/chat/state.cjs +14 -1
- package/dist/chat/state.cjs.map +1 -1
- package/dist/chat/state.d.cts +5 -2
- package/dist/chat/state.d.ts +5 -2
- package/dist/chat/state.js +14 -1
- package/dist/chat/state.js.map +1 -1
- package/dist/chat/storage.cjs +19 -10
- package/dist/chat/storage.cjs.map +1 -1
- package/dist/chat/storage.d.cts +11 -5
- package/dist/chat/storage.d.ts +11 -5
- package/dist/chat/storage.js +19 -10
- package/dist/chat/storage.js.map +1 -1
- package/dist/errors-C-so0M4t.d.cts +33 -0
- package/dist/errors-C-so0M4t.d.ts +33 -0
- package/dist/errors-CmVvczxZ.d.cts +28 -0
- package/dist/errors-CmVvczxZ.d.ts +28 -0
- package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-C1JnJGVR.d.ts} +28 -23
- package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-C7DSqPyX.d.cts} +28 -23
- package/dist/index.cjs +340 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +292 -123
- package/dist/index.d.ts +292 -123
- package/dist/index.js +334 -47
- package/dist/index.js.map +1 -1
- package/dist/provider-types-PTSlRPNB.d.cts +39 -0
- package/dist/provider-types-PTSlRPNB.d.ts +39 -0
- package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
- package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
- package/dist/testing.cjs +383 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +132 -0
- package/dist/testing.d.ts +132 -0
- package/dist/testing.js +377 -0
- package/dist/testing.js.map +1 -0
- package/dist/token-store-CSUBgYwn.d.ts +48 -0
- package/dist/token-store-CuC4hB9Z.d.cts +48 -0
- package/dist/{transport-DX1Nhm4N.d.cts → transport-Cdh3M0tS.d.cts} +5 -4
- package/dist/{transport-D1OaUgRk.d.ts → transport-Ciap4PWK.d.ts} +5 -4
- package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
- package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
- package/dist/types-DRgd_9R7.d.cts +363 -0
- package/dist/types-ajANVzf7.d.ts +363 -0
- package/package.json +31 -6
- package/dist/errors-BDLbNu9w.d.cts +0 -13
- package/dist/errors-BDLbNu9w.d.ts +0 -13
- package/dist/types-DLZzlJxt.d.ts +0 -39
- package/dist/types-tE0CXwBl.d.cts +0 -39
package/dist/chat/runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/chat/
|
|
1
|
+
// src/chat/types.ts
|
|
2
2
|
function createChatId() {
|
|
3
3
|
return crypto.randomUUID();
|
|
4
4
|
}
|
|
@@ -9,6 +9,8 @@ function toChatId(value) {
|
|
|
9
9
|
}
|
|
10
10
|
return value;
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
// src/chat/bridge.ts
|
|
12
14
|
function chatEventToAgentEvent(event) {
|
|
13
15
|
switch (event.type) {
|
|
14
16
|
case "message:delta":
|
|
@@ -34,7 +36,7 @@ function chatEventToAgentEvent(event) {
|
|
|
34
36
|
result: event.result
|
|
35
37
|
};
|
|
36
38
|
case "error":
|
|
37
|
-
return { type: "error", error: event.error, recoverable: event.recoverable };
|
|
39
|
+
return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
|
|
38
40
|
default:
|
|
39
41
|
return null;
|
|
40
42
|
}
|
|
@@ -148,6 +150,56 @@ var ContextWindowManager = class {
|
|
|
148
150
|
});
|
|
149
151
|
return { ...result, messages: updatedMessages };
|
|
150
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Trim messages using real token usage data from the previous API call.
|
|
155
|
+
* Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
|
|
156
|
+
* Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
|
|
157
|
+
*
|
|
158
|
+
* @param messages - All messages in the session
|
|
159
|
+
* @param lastPromptTokens - Real prompt tokens from the last API response
|
|
160
|
+
* @param modelContextWindow - Model's total context window size in tokens
|
|
161
|
+
* @returns Result with fitted messages and metadata
|
|
162
|
+
*/
|
|
163
|
+
fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
|
|
164
|
+
if (messages.length === 0) {
|
|
165
|
+
return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
|
|
166
|
+
}
|
|
167
|
+
const budget = modelContextWindow - this.config.reservedTokens;
|
|
168
|
+
if (budget <= 0 || lastPromptTokens <= budget) {
|
|
169
|
+
return {
|
|
170
|
+
messages: [...messages],
|
|
171
|
+
totalTokens: lastPromptTokens,
|
|
172
|
+
removedCount: 0,
|
|
173
|
+
wasTruncated: false
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const avgTokensPerMessage = lastPromptTokens / messages.length;
|
|
177
|
+
const tokensToFree = lastPromptTokens - budget;
|
|
178
|
+
const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
|
|
179
|
+
const nonSystemIndices = [];
|
|
180
|
+
for (let i = 0; i < messages.length; i++) {
|
|
181
|
+
if (messages[i].role === "system") ; else {
|
|
182
|
+
nonSystemIndices.push(i);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
|
|
186
|
+
const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
|
|
187
|
+
const result = [];
|
|
188
|
+
for (let i = 0; i < messages.length; i++) {
|
|
189
|
+
if (!removedIndices.has(i)) {
|
|
190
|
+
result.push(messages[i]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const estimatedTokens = Math.round(
|
|
194
|
+
lastPromptTokens * (result.length / messages.length)
|
|
195
|
+
);
|
|
196
|
+
return {
|
|
197
|
+
messages: result,
|
|
198
|
+
totalTokens: estimatedTokens,
|
|
199
|
+
removedCount: removableCount,
|
|
200
|
+
wasTruncated: removableCount > 0
|
|
201
|
+
};
|
|
202
|
+
}
|
|
151
203
|
/**
|
|
152
204
|
* Truncate oldest: keeps system messages, removes oldest non-system messages first.
|
|
153
205
|
* Always keeps the most recent user message.
|
|
@@ -268,9 +320,18 @@ var ContextWindowManager = class {
|
|
|
268
320
|
var AgentSDKError = class extends Error {
|
|
269
321
|
/** @internal Marker for cross-bundle identity checks */
|
|
270
322
|
_agentSDKError = true;
|
|
323
|
+
/** Machine-readable error code. Prefer values from the ErrorCode enum. */
|
|
324
|
+
code;
|
|
325
|
+
/** Whether this error is safe to retry */
|
|
326
|
+
retryable;
|
|
327
|
+
/** HTTP status code hint for error classification */
|
|
328
|
+
httpStatus;
|
|
271
329
|
constructor(message, options) {
|
|
272
330
|
super(message, options);
|
|
273
331
|
this.name = "AgentSDKError";
|
|
332
|
+
this.code = options?.code;
|
|
333
|
+
this.retryable = options?.retryable ?? false;
|
|
334
|
+
this.httpStatus = options?.httpStatus;
|
|
274
335
|
}
|
|
275
336
|
/** Check if an error is an AgentSDKError (works across bundled copies) */
|
|
276
337
|
static is(error) {
|
|
@@ -285,7 +346,11 @@ var ChatError = class extends AgentSDKError {
|
|
|
285
346
|
retryAfter;
|
|
286
347
|
timestamp;
|
|
287
348
|
constructor(message, options) {
|
|
288
|
-
super(message, {
|
|
349
|
+
super(message, {
|
|
350
|
+
cause: options.cause,
|
|
351
|
+
code: options.code,
|
|
352
|
+
retryable: options.retryable
|
|
353
|
+
});
|
|
289
354
|
this.name = "ChatError";
|
|
290
355
|
this.code = options.code;
|
|
291
356
|
this.retryable = options.retryable ?? false;
|
|
@@ -606,6 +671,35 @@ var CancellableTimeout = class {
|
|
|
606
671
|
}
|
|
607
672
|
};
|
|
608
673
|
|
|
674
|
+
// src/chat/listener-set.ts
|
|
675
|
+
var ListenerSet = class {
|
|
676
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
677
|
+
/** Add a listener. Returns an unsubscribe function. */
|
|
678
|
+
add(callback) {
|
|
679
|
+
this._listeners.add(callback);
|
|
680
|
+
return () => {
|
|
681
|
+
this._listeners.delete(callback);
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
/** Notify all listeners with the given arguments. Errors are isolated per listener. */
|
|
685
|
+
notify(...args) {
|
|
686
|
+
for (const cb of this._listeners) {
|
|
687
|
+
try {
|
|
688
|
+
cb(...args);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/** Remove all listeners. */
|
|
694
|
+
clear() {
|
|
695
|
+
this._listeners.clear();
|
|
696
|
+
}
|
|
697
|
+
/** Current number of listeners. */
|
|
698
|
+
get size() {
|
|
699
|
+
return this._listeners.size;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
609
703
|
// src/chat/runtime.ts
|
|
610
704
|
var ChatRuntime = class {
|
|
611
705
|
_state;
|
|
@@ -617,20 +711,19 @@ var ChatRuntime = class {
|
|
|
617
711
|
_tools = /* @__PURE__ */ new Map();
|
|
618
712
|
_retryConfig;
|
|
619
713
|
_contextStats = /* @__PURE__ */ new Map();
|
|
714
|
+
_sessionUsage = /* @__PURE__ */ new Map();
|
|
715
|
+
_modelContextWindows = /* @__PURE__ */ new Map();
|
|
620
716
|
_onContextTrimmed;
|
|
621
717
|
_streamTimeoutMs;
|
|
622
|
-
_sessionListeners =
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
_currentModel;
|
|
626
|
-
_activeSessionId = null;
|
|
718
|
+
_sessionListeners = new ListenerSet();
|
|
719
|
+
_adapterPool = /* @__PURE__ */ new Map();
|
|
720
|
+
_defaultBackend;
|
|
627
721
|
_abortController = null;
|
|
628
722
|
constructor(options) {
|
|
629
723
|
this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
|
|
630
724
|
this._guard = new ChatReentrancyGuard();
|
|
631
725
|
this._backends = options.backends;
|
|
632
|
-
this.
|
|
633
|
-
this._currentModel = options.defaultModel;
|
|
726
|
+
this._defaultBackend = options.defaultBackend;
|
|
634
727
|
this._sessionStore = options.sessionStore;
|
|
635
728
|
this._contextConfig = options.context;
|
|
636
729
|
this._middleware = [...options.middleware ?? []];
|
|
@@ -643,6 +736,11 @@ var ChatRuntime = class {
|
|
|
643
736
|
{ code: "INVALID_INPUT" /* INVALID_INPUT */ }
|
|
644
737
|
);
|
|
645
738
|
}
|
|
739
|
+
if (options.tools) {
|
|
740
|
+
for (const tool of options.tools) {
|
|
741
|
+
this._tools.set(tool.name, tool);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
646
744
|
}
|
|
647
745
|
// ── Lifecycle ──────────────────────────────────────────────
|
|
648
746
|
get status() {
|
|
@@ -654,24 +752,23 @@ var ChatRuntime = class {
|
|
|
654
752
|
this._abortController?.dispose();
|
|
655
753
|
this._abortController = null;
|
|
656
754
|
this._state.transition("disposed");
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
755
|
+
for (const adapter of this._adapterPool.values()) {
|
|
756
|
+
try {
|
|
757
|
+
await adapter.dispose();
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
660
760
|
}
|
|
761
|
+
this._adapterPool.clear();
|
|
661
762
|
}
|
|
662
763
|
// ── Sessions ───────────────────────────────────────────────
|
|
663
|
-
get activeSessionId() {
|
|
664
|
-
return this._activeSessionId;
|
|
665
|
-
}
|
|
666
764
|
async createSession(options) {
|
|
667
765
|
this.assertNotDisposed();
|
|
668
766
|
const config = {
|
|
669
|
-
model: options.config?.model ??
|
|
670
|
-
backend: options.config?.backend ?? this.
|
|
767
|
+
model: options.config?.model ?? "",
|
|
768
|
+
backend: options.config?.backend ?? this._defaultBackend,
|
|
671
769
|
...options.config
|
|
672
770
|
};
|
|
673
771
|
const session = await this._sessionStore.createSession({ ...options, config });
|
|
674
|
-
this._activeSessionId = session.id;
|
|
675
772
|
this._notifySessionChange();
|
|
676
773
|
return session;
|
|
677
774
|
}
|
|
@@ -691,36 +788,12 @@ var ChatRuntime = class {
|
|
|
691
788
|
if (!session) return;
|
|
692
789
|
await this._sessionStore.deleteSession(cid);
|
|
693
790
|
this._contextStats.delete(cid);
|
|
694
|
-
|
|
695
|
-
this._activeSessionId = null;
|
|
696
|
-
}
|
|
791
|
+
this._sessionUsage.delete(cid);
|
|
697
792
|
this._notifySessionChange();
|
|
698
793
|
}
|
|
699
|
-
async archiveSession(id) {
|
|
700
|
-
this.assertNotDisposed();
|
|
701
|
-
const cid = toChatId(id);
|
|
702
|
-
await this._sessionStore.archiveSession(cid);
|
|
703
|
-
this._notifySessionChange();
|
|
704
|
-
}
|
|
705
|
-
async switchSession(id) {
|
|
706
|
-
this.assertNotDisposed();
|
|
707
|
-
const cid = toChatId(id);
|
|
708
|
-
const session = await this._sessionStore.getSession(cid);
|
|
709
|
-
if (!session) {
|
|
710
|
-
throw new ChatError(
|
|
711
|
-
`Session "${id}" not found`,
|
|
712
|
-
{ code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
this._activeSessionId = session.id;
|
|
716
|
-
return session;
|
|
717
|
-
}
|
|
718
794
|
// ── Messaging ──────────────────────────────────────────────
|
|
719
795
|
async *send(sessionId, message, options) {
|
|
720
|
-
this.
|
|
721
|
-
if (!message || message.trim().length === 0) {
|
|
722
|
-
throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
|
|
723
|
-
}
|
|
796
|
+
this.validateSendInput(message, options);
|
|
724
797
|
this._guard.acquire();
|
|
725
798
|
const cid = toChatId(sessionId);
|
|
726
799
|
this._abortController = new ChatAbortController(options?.signal);
|
|
@@ -729,150 +802,274 @@ var ChatRuntime = class {
|
|
|
729
802
|
this._state.transition("idle");
|
|
730
803
|
}
|
|
731
804
|
this._state.transition("streaming");
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
throw new ChatError(
|
|
735
|
-
`Session "${cid}" not found`,
|
|
736
|
-
{ code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
const middlewareContext = {
|
|
805
|
+
await this.loadSession(cid);
|
|
806
|
+
const mwCtx = {
|
|
740
807
|
sessionId: cid,
|
|
741
808
|
signal: this._abortController.signal
|
|
742
809
|
};
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const updatedSession = await this._sessionStore.getSession(cid);
|
|
751
|
-
let messagesToSend = updatedSession.messages;
|
|
752
|
-
if (this._contextConfig) {
|
|
753
|
-
const ctxManager = new ContextWindowManager(this._contextConfig);
|
|
754
|
-
const result = await ctxManager.fitMessagesAsync(messagesToSend);
|
|
755
|
-
this._contextStats.set(cid, {
|
|
756
|
-
totalTokens: result.totalTokens,
|
|
757
|
-
removedCount: result.removedCount,
|
|
758
|
-
wasTruncated: result.wasTruncated,
|
|
759
|
-
availableBudget: ctxManager.availableBudget
|
|
760
|
-
});
|
|
761
|
-
if (result.wasTruncated && this._onContextTrimmed) {
|
|
762
|
-
const keptIds = new Set(result.messages.map((m) => m.id));
|
|
763
|
-
const removed = messagesToSend.filter((m) => !keptIds.has(m.id));
|
|
764
|
-
if (removed.length > 0) {
|
|
765
|
-
try {
|
|
766
|
-
this._onContextTrimmed(cid, removed);
|
|
767
|
-
} catch {
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
messagesToSend = result.messages;
|
|
810
|
+
const userMessage = await this.applyBeforeSendMiddleware(
|
|
811
|
+
this.createUserMessage(message),
|
|
812
|
+
mwCtx
|
|
813
|
+
);
|
|
814
|
+
if (userMessage === null) {
|
|
815
|
+
this._state.transition("idle");
|
|
816
|
+
return;
|
|
772
817
|
}
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
818
|
+
const updatedSession = await this.persistAndReload(cid, userMessage);
|
|
819
|
+
const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
|
|
820
|
+
const stream = await this.prepareEventStream(
|
|
821
|
+
cid,
|
|
822
|
+
sessionForAdapter,
|
|
823
|
+
updatedSession,
|
|
824
|
+
message,
|
|
825
|
+
options
|
|
826
|
+
);
|
|
778
827
|
const accumulator = new MessageAccumulator();
|
|
779
|
-
const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
|
|
780
|
-
sessionId: cid,
|
|
781
|
-
custom: updatedSession.metadata?.custom
|
|
782
|
-
}) : void 0;
|
|
783
|
-
const streamOptions = {
|
|
784
|
-
...options,
|
|
785
|
-
signal: this._abortController.signal,
|
|
786
|
-
model: options?.model ?? this._currentModel,
|
|
787
|
-
tools: runtimeTools
|
|
788
|
-
};
|
|
789
|
-
const stream = await this.createStreamWithRetry(adapter, sessionForAdapter, message, streamOptions);
|
|
790
828
|
const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
|
|
791
829
|
for await (const event of eventSource) {
|
|
792
830
|
if (this._abortController.isAborted) break;
|
|
793
831
|
this.feedAccumulator(accumulator, event);
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
if (processedEvent) {
|
|
801
|
-
yield processedEvent;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
if (this._state.current === "disposed") {
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
let assistantMessage = accumulator.finalize();
|
|
808
|
-
for (const mw of this._middleware) {
|
|
809
|
-
if (mw.onAfterReceive) {
|
|
810
|
-
assistantMessage = await mw.onAfterReceive(assistantMessage, middlewareContext);
|
|
832
|
+
if (event.type === "usage") {
|
|
833
|
+
this._sessionUsage.set(cid, {
|
|
834
|
+
promptTokens: event.promptTokens,
|
|
835
|
+
completionTokens: event.completionTokens
|
|
836
|
+
});
|
|
837
|
+
this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
|
|
811
838
|
}
|
|
839
|
+
const processed = await this.applyOnEventMiddleware(event, mwCtx);
|
|
840
|
+
if (processed) yield processed;
|
|
812
841
|
}
|
|
813
|
-
|
|
814
|
-
this.
|
|
842
|
+
if (this._state.current === "disposed") return;
|
|
843
|
+
await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
|
|
815
844
|
this._state.transition("idle");
|
|
816
845
|
} catch (error) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
sessionId: cid,
|
|
820
|
-
signal: this._abortController?.signal ?? new AbortController().signal
|
|
821
|
-
};
|
|
822
|
-
for (const mw of this._middleware) {
|
|
823
|
-
if (mw.onError) {
|
|
824
|
-
const result = await mw.onError(processedError, middlewareContext);
|
|
825
|
-
if (result === null) {
|
|
826
|
-
if (this._state.canTransition("idle")) {
|
|
827
|
-
this._state.transition("idle");
|
|
828
|
-
}
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
processedError = result;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
if (this._state.canTransition("error")) {
|
|
835
|
-
this._state.transition("error");
|
|
836
|
-
}
|
|
837
|
-
throw processedError;
|
|
846
|
+
const result = await this.handleSendError(error, cid);
|
|
847
|
+
if (result !== null) throw result;
|
|
838
848
|
} finally {
|
|
839
849
|
this._guard.release();
|
|
840
850
|
this._abortController?.dispose();
|
|
841
851
|
this._abortController = null;
|
|
842
852
|
}
|
|
843
853
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
// ── Backend / Model ────────────────────────────────────────
|
|
848
|
-
get currentBackend() {
|
|
849
|
-
return this._currentBackend;
|
|
850
|
-
}
|
|
851
|
-
get currentModel() {
|
|
852
|
-
return this._currentModel;
|
|
853
|
-
}
|
|
854
|
-
async switchBackend(name) {
|
|
854
|
+
// ── Send Pipeline Stages ──────────────────────────────────────
|
|
855
|
+
/** Stage 1: Validate send inputs (message content + required fields). */
|
|
856
|
+
validateSendInput(message, options) {
|
|
855
857
|
this.assertNotDisposed();
|
|
856
|
-
if (!
|
|
858
|
+
if (!message || message.trim().length === 0) {
|
|
859
|
+
throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
|
|
860
|
+
}
|
|
861
|
+
if (!options.model) {
|
|
862
|
+
throw new ChatError(
|
|
863
|
+
"options.model is required \u2014 caller must specify which model to use",
|
|
864
|
+
{ code: "INVALID_INPUT" /* INVALID_INPUT */ }
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
if (!options.backend) {
|
|
868
|
+
throw new ChatError(
|
|
869
|
+
"options.backend is required \u2014 caller must specify which backend to use",
|
|
870
|
+
{ code: "INVALID_INPUT" /* INVALID_INPUT */ }
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
if (!options.credentials) {
|
|
857
874
|
throw new ChatError(
|
|
858
|
-
|
|
875
|
+
"options.credentials is required \u2014 caller must provide authentication credentials",
|
|
859
876
|
{ code: "INVALID_INPUT" /* INVALID_INPUT */ }
|
|
860
877
|
);
|
|
861
878
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
879
|
+
}
|
|
880
|
+
/** Stage 2: Load session from store. */
|
|
881
|
+
async loadSession(cid) {
|
|
882
|
+
const session = await this._sessionStore.getSession(cid);
|
|
883
|
+
if (!session) {
|
|
884
|
+
throw new ChatError(
|
|
885
|
+
`Session "${cid}" not found`,
|
|
886
|
+
{ code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
return session;
|
|
890
|
+
}
|
|
891
|
+
/** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
|
|
892
|
+
async applyBeforeSendMiddleware(userMessage, ctx) {
|
|
893
|
+
let msg = userMessage;
|
|
894
|
+
for (const mw of this._middleware) {
|
|
895
|
+
if (mw.onBeforeSend && msg) {
|
|
896
|
+
msg = await mw.onBeforeSend(msg, ctx);
|
|
897
|
+
if (msg === null) return null;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return msg;
|
|
901
|
+
}
|
|
902
|
+
/** Stage 4: Persist user message and reload session with full history. */
|
|
903
|
+
async persistAndReload(cid, userMessage) {
|
|
904
|
+
await this._sessionStore.appendMessage(cid, userMessage);
|
|
905
|
+
return await this._sessionStore.getSession(cid);
|
|
906
|
+
}
|
|
907
|
+
/** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
|
|
908
|
+
async trimSessionContext(cid, session, model) {
|
|
909
|
+
if (!this._contextConfig) return session;
|
|
910
|
+
const ctxManager = new ContextWindowManager(this._contextConfig);
|
|
911
|
+
const lastUsage = this._sessionUsage.get(cid);
|
|
912
|
+
const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
|
|
913
|
+
if (lastUsage && modelContextWindow) {
|
|
914
|
+
const result2 = ctxManager.fitMessagesWithUsage(
|
|
915
|
+
session.messages,
|
|
916
|
+
lastUsage.promptTokens,
|
|
917
|
+
modelContextWindow
|
|
918
|
+
);
|
|
919
|
+
this._contextStats.set(cid, {
|
|
920
|
+
totalTokens: result2.totalTokens,
|
|
921
|
+
removedCount: result2.removedCount,
|
|
922
|
+
wasTruncated: result2.wasTruncated,
|
|
923
|
+
availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
|
|
924
|
+
realPromptTokens: lastUsage.promptTokens,
|
|
925
|
+
realCompletionTokens: lastUsage.completionTokens,
|
|
926
|
+
modelContextWindow
|
|
927
|
+
});
|
|
928
|
+
if (result2.wasTruncated && this._onContextTrimmed) {
|
|
929
|
+
const keptIds = new Set(result2.messages.map((m) => m.id));
|
|
930
|
+
const removed = session.messages.filter((m) => !keptIds.has(m.id));
|
|
931
|
+
if (removed.length > 0) {
|
|
932
|
+
try {
|
|
933
|
+
this._onContextTrimmed(cid, removed);
|
|
934
|
+
} catch {
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return { ...session, messages: result2.messages };
|
|
939
|
+
}
|
|
940
|
+
const result = await ctxManager.fitMessagesAsync(session.messages);
|
|
941
|
+
this._contextStats.set(cid, {
|
|
942
|
+
totalTokens: result.totalTokens,
|
|
943
|
+
removedCount: result.removedCount,
|
|
944
|
+
wasTruncated: result.wasTruncated,
|
|
945
|
+
availableBudget: ctxManager.availableBudget,
|
|
946
|
+
modelContextWindow
|
|
947
|
+
});
|
|
948
|
+
if (result.wasTruncated && this._onContextTrimmed) {
|
|
949
|
+
const keptIds = new Set(result.messages.map((m) => m.id));
|
|
950
|
+
const removed = session.messages.filter((m) => !keptIds.has(m.id));
|
|
951
|
+
if (removed.length > 0) {
|
|
952
|
+
try {
|
|
953
|
+
this._onContextTrimmed(cid, removed);
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return { ...session, messages: result.messages };
|
|
959
|
+
}
|
|
960
|
+
/** Update context stats with real usage data from a usage event. */
|
|
961
|
+
updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
|
|
962
|
+
const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
|
|
963
|
+
const existing = this._contextStats.get(cid);
|
|
964
|
+
this._contextStats.set(cid, {
|
|
965
|
+
totalTokens: promptTokens,
|
|
966
|
+
removedCount: existing?.removedCount ?? 0,
|
|
967
|
+
wasTruncated: existing?.wasTruncated ?? false,
|
|
968
|
+
availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
|
|
969
|
+
realPromptTokens: promptTokens,
|
|
970
|
+
realCompletionTokens: completionTokens,
|
|
971
|
+
modelContextWindow
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
/** Stage 6: Prepare event stream — adapter with retry, tool injection. */
|
|
975
|
+
async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
|
|
976
|
+
const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
|
|
977
|
+
const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
|
|
978
|
+
sessionId: cid,
|
|
979
|
+
custom: fullSession.metadata?.custom
|
|
980
|
+
}) : void 0;
|
|
981
|
+
const streamOptions = {
|
|
982
|
+
signal: this._abortController.signal,
|
|
983
|
+
model: options.model,
|
|
984
|
+
systemPrompt: options.systemPrompt,
|
|
985
|
+
tools: runtimeTools
|
|
986
|
+
};
|
|
987
|
+
return this.createStreamWithRetry(
|
|
988
|
+
adapter,
|
|
989
|
+
sessionForAdapter,
|
|
990
|
+
message,
|
|
991
|
+
streamOptions,
|
|
992
|
+
options.backend,
|
|
993
|
+
options.credentials
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
/** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
|
|
997
|
+
async applyOnEventMiddleware(event, ctx) {
|
|
998
|
+
let processed = event;
|
|
999
|
+
for (const mw of this._middleware) {
|
|
1000
|
+
if (mw.onEvent && processed) {
|
|
1001
|
+
processed = await mw.onEvent(processed, ctx);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return processed;
|
|
1005
|
+
}
|
|
1006
|
+
/** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
|
|
1007
|
+
async finalizeAssistantMessage(cid, accumulator, ctx) {
|
|
1008
|
+
let assistantMessage = accumulator.finalize();
|
|
1009
|
+
for (const mw of this._middleware) {
|
|
1010
|
+
if (mw.onAfterReceive) {
|
|
1011
|
+
assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
await this._sessionStore.appendMessage(cid, assistantMessage);
|
|
1015
|
+
this._notifySessionChange();
|
|
1016
|
+
}
|
|
1017
|
+
/** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
|
|
1018
|
+
async handleSendError(error, cid) {
|
|
1019
|
+
let processedError = error instanceof Error ? error : new Error(String(error));
|
|
1020
|
+
const ctx = {
|
|
1021
|
+
sessionId: cid,
|
|
1022
|
+
signal: this._abortController?.signal ?? new AbortController().signal
|
|
1023
|
+
};
|
|
1024
|
+
for (const mw of this._middleware) {
|
|
1025
|
+
if (mw.onError) {
|
|
1026
|
+
const result = await mw.onError(processedError, ctx);
|
|
1027
|
+
if (result === null) {
|
|
1028
|
+
if (this._state.canTransition("idle")) {
|
|
1029
|
+
this._state.transition("idle");
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
processedError = result;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (this._state.canTransition("error")) {
|
|
1037
|
+
this._state.transition("error");
|
|
865
1038
|
}
|
|
866
|
-
|
|
1039
|
+
return processedError;
|
|
867
1040
|
}
|
|
868
|
-
|
|
1041
|
+
abort() {
|
|
1042
|
+
this._abortController?.abort("User abort");
|
|
1043
|
+
}
|
|
1044
|
+
// ── Backend / Model ────────────────────────────────────────
|
|
1045
|
+
async listModels(options) {
|
|
869
1046
|
this.assertNotDisposed();
|
|
870
|
-
|
|
1047
|
+
let models = [];
|
|
1048
|
+
const firstAdapter = [...this._adapterPool.values()][0];
|
|
1049
|
+
if (firstAdapter) {
|
|
1050
|
+
try {
|
|
1051
|
+
models = await firstAdapter.listModels();
|
|
1052
|
+
} catch {
|
|
1053
|
+
return [];
|
|
1054
|
+
}
|
|
1055
|
+
} else if (options?.backend && options?.credentials) {
|
|
1056
|
+
try {
|
|
1057
|
+
const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
|
|
1058
|
+
models = await adapter.listModels();
|
|
1059
|
+
} catch {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
for (const model of models) {
|
|
1064
|
+
if (model.contextWindow != null) {
|
|
1065
|
+
this._modelContextWindows.set(model.id, model.contextWindow);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return models;
|
|
871
1069
|
}
|
|
872
|
-
async
|
|
1070
|
+
async listBackends() {
|
|
873
1071
|
this.assertNotDisposed();
|
|
874
|
-
|
|
875
|
-
return adapter.listModels();
|
|
1072
|
+
return Object.keys(this._backends).map((name) => ({ name }));
|
|
876
1073
|
}
|
|
877
1074
|
// ── Tools ──────────────────────────────────────────────────
|
|
878
1075
|
get registeredTools() {
|
|
@@ -897,37 +1094,46 @@ var ChatRuntime = class {
|
|
|
897
1094
|
if (idx >= 0) this._middleware.splice(idx, 1);
|
|
898
1095
|
}
|
|
899
1096
|
// ── Context Stats ─────────────────────────────────────────
|
|
900
|
-
getContextStats(sessionId) {
|
|
1097
|
+
async getContextStats(sessionId) {
|
|
901
1098
|
const cid = toChatId(sessionId);
|
|
902
1099
|
return this._contextStats.get(cid) ?? null;
|
|
903
1100
|
}
|
|
904
1101
|
// ── Session Subscription ──────────────────────────────────
|
|
905
1102
|
onSessionChange(callback) {
|
|
906
|
-
this._sessionListeners.add(callback);
|
|
907
|
-
return () => {
|
|
908
|
-
this._sessionListeners.delete(callback);
|
|
909
|
-
};
|
|
1103
|
+
return this._sessionListeners.add(callback);
|
|
910
1104
|
}
|
|
911
1105
|
_notifySessionChange() {
|
|
912
|
-
|
|
913
|
-
try {
|
|
914
|
-
cb();
|
|
915
|
-
} catch {
|
|
916
|
-
}
|
|
917
|
-
}
|
|
1106
|
+
this._sessionListeners.notify();
|
|
918
1107
|
}
|
|
919
1108
|
// ── Private Helpers ────────────────────────────────────────
|
|
920
|
-
async getOrCreateAdapter() {
|
|
921
|
-
|
|
922
|
-
const
|
|
1109
|
+
async getOrCreateAdapter(backend, credentials) {
|
|
1110
|
+
const key = this.getPoolKey(backend, credentials);
|
|
1111
|
+
const existing = this._adapterPool.get(key);
|
|
1112
|
+
if (existing) return existing;
|
|
1113
|
+
for (const [oldKey, oldAdapter] of this._adapterPool) {
|
|
1114
|
+
if (oldKey.startsWith(backend + ":")) {
|
|
1115
|
+
try {
|
|
1116
|
+
await oldAdapter.dispose();
|
|
1117
|
+
} catch {
|
|
1118
|
+
}
|
|
1119
|
+
this._adapterPool.delete(oldKey);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const factory = this._backends[backend];
|
|
923
1123
|
if (!factory) {
|
|
924
1124
|
throw new ChatError(
|
|
925
|
-
`Backend "${
|
|
1125
|
+
`Backend "${backend}" not found`,
|
|
926
1126
|
{ code: "INVALID_INPUT" /* INVALID_INPUT */ }
|
|
927
1127
|
);
|
|
928
1128
|
}
|
|
929
|
-
|
|
930
|
-
|
|
1129
|
+
const adapter = await factory(credentials);
|
|
1130
|
+
this._adapterPool.set(key, adapter);
|
|
1131
|
+
return adapter;
|
|
1132
|
+
}
|
|
1133
|
+
getPoolKey(backend, credentials) {
|
|
1134
|
+
const token = credentials.accessToken;
|
|
1135
|
+
const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
|
|
1136
|
+
return `${backend}:${hash}`;
|
|
931
1137
|
}
|
|
932
1138
|
/** Wrap each tool's execute to inject ToolContext as 2nd argument */
|
|
933
1139
|
injectToolContext(tools, context) {
|
|
@@ -959,17 +1165,25 @@ var ChatRuntime = class {
|
|
|
959
1165
|
}
|
|
960
1166
|
}
|
|
961
1167
|
/** Get or create adapter with retry on connection errors */
|
|
962
|
-
async getOrCreateAdapterWithRetry() {
|
|
1168
|
+
async getOrCreateAdapterWithRetry(backend, credentials) {
|
|
963
1169
|
const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
|
|
964
1170
|
const delayMs = this._retryConfig?.delayMs ?? 0;
|
|
965
1171
|
let lastError;
|
|
966
1172
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
967
1173
|
try {
|
|
968
|
-
return await this.getOrCreateAdapter();
|
|
1174
|
+
return await this.getOrCreateAdapter(backend, credentials);
|
|
969
1175
|
} catch (err) {
|
|
970
1176
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
971
1177
|
if (attempt < maxAttempts) {
|
|
972
|
-
this.
|
|
1178
|
+
const key = this.getPoolKey(backend, credentials);
|
|
1179
|
+
const old = this._adapterPool.get(key);
|
|
1180
|
+
if (old) {
|
|
1181
|
+
try {
|
|
1182
|
+
await old.dispose();
|
|
1183
|
+
} catch {
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
this._adapterPool.delete(key);
|
|
973
1187
|
await delay(delayMs);
|
|
974
1188
|
}
|
|
975
1189
|
}
|
|
@@ -982,7 +1196,7 @@ var ChatRuntime = class {
|
|
|
982
1196
|
* retries with a fresh adapter. Once first event is received,
|
|
983
1197
|
* the stream is committed (no more retries).
|
|
984
1198
|
*/
|
|
985
|
-
async createStreamWithRetry(adapter, session, message, options) {
|
|
1199
|
+
async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
|
|
986
1200
|
const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
|
|
987
1201
|
const delayMs = this._retryConfig?.delayMs ?? 0;
|
|
988
1202
|
let lastError;
|
|
@@ -1003,13 +1217,14 @@ var ChatRuntime = class {
|
|
|
1003
1217
|
} catch (err) {
|
|
1004
1218
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1005
1219
|
if (attempt < maxAttempts) {
|
|
1006
|
-
|
|
1007
|
-
await
|
|
1008
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
await currentAdapter.dispose();
|
|
1222
|
+
} catch {
|
|
1009
1223
|
}
|
|
1010
|
-
this.
|
|
1224
|
+
const key = this.getPoolKey(backend, credentials);
|
|
1225
|
+
this._adapterPool.delete(key);
|
|
1011
1226
|
await delay(delayMs);
|
|
1012
|
-
currentAdapter = await this.getOrCreateAdapter();
|
|
1227
|
+
currentAdapter = await this.getOrCreateAdapter(backend, credentials);
|
|
1013
1228
|
}
|
|
1014
1229
|
}
|
|
1015
1230
|
}
|