@stacklink/sdk 0.2.0 → 0.2.2
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 +10 -0
- package/compatibility.json +1 -1
- package/dist/src/domain/memory-contract.d.ts +1497 -335
- package/dist/src/domain/memory-contract.d.ts.map +1 -1
- package/dist/src/domain/memory-contract.js +330 -59
- package/dist/src/domain/memory-contract.js.map +1 -1
- package/dist/src/domain/provider-branding.d.ts +1 -1
- package/dist/src/domain/provider-branding.d.ts.map +1 -1
- package/dist/src/domain/provider-branding.js +296 -8
- package/dist/src/domain/provider-branding.js.map +1 -1
- package/dist/src/domain/stacklink-meta-tools.d.ts +14 -16
- package/dist/src/domain/stacklink-meta-tools.d.ts.map +1 -1
- package/dist/src/domain/stacklink-meta-tools.js +205 -127
- package/dist/src/domain/stacklink-meta-tools.js.map +1 -1
- package/dist/src/sdk/framework-tools.d.ts +3 -2
- package/dist/src/sdk/framework-tools.d.ts.map +1 -1
- package/dist/src/sdk/framework-tools.js +1 -0
- package/dist/src/sdk/framework-tools.js.map +1 -1
- package/dist/src/sdk/index.d.ts +280 -142
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +433 -224
- package/dist/src/sdk/index.js.map +1 -1
- package/dist/src/sdk/langchain.d.ts +7 -2
- package/dist/src/sdk/langchain.d.ts.map +1 -1
- package/dist/src/sdk/langchain.js +3 -2
- package/dist/src/sdk/langchain.js.map +1 -1
- package/dist/src/sdk/memory-lifecycle.d.ts +25 -0
- package/dist/src/sdk/memory-lifecycle.d.ts.map +1 -1
- package/dist/src/sdk/memory-lifecycle.js +75 -1
- package/dist/src/sdk/memory-lifecycle.js.map +1 -1
- package/dist/src/sdk/openai-agents.d.ts +6 -6
- package/dist/src/sdk/openai-agents.d.ts.map +1 -1
- package/dist/src/sdk/openai-agents.js +21 -16
- package/dist/src/sdk/openai-agents.js.map +1 -1
- package/dist/src/sdk/openai.d.ts +2 -2
- package/dist/src/sdk/openai.d.ts.map +1 -1
- package/dist/src/sdk/openai.js +67 -31
- package/dist/src/sdk/openai.js.map +1 -1
- package/dist/src/sdk/vercel-ai.d.ts +11 -4
- package/dist/src/sdk/vercel-ai.d.ts.map +1 -1
- package/dist/src/sdk/vercel-ai.js +21 -6
- package/dist/src/sdk/vercel-ai.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/domain/knowledge-meta-tools.d.ts +0 -12
- package/dist/src/domain/knowledge-meta-tools.d.ts.map +0 -1
- package/dist/src/domain/knowledge-meta-tools.js +0 -38
- package/dist/src/domain/knowledge-meta-tools.js.map +0 -1
- package/dist/src/domain/memory-meta-tools.d.ts +0 -5
- package/dist/src/domain/memory-meta-tools.d.ts.map +0 -1
- package/dist/src/domain/memory-meta-tools.js +0 -18
- package/dist/src/domain/memory-meta-tools.js.map +0 -1
- package/dist/src/domain/platform-mcp-tools.d.ts +0 -3
- package/dist/src/domain/platform-mcp-tools.d.ts.map +0 -1
- package/dist/src/domain/platform-mcp-tools.js +0 -30
- package/dist/src/domain/platform-mcp-tools.js.map +0 -1
package/dist/src/sdk/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { materializeStacklinkWorkbenchExecutionResponse, } from '../domain/stacklink-meta-tools.js';
|
|
2
|
-
import { platformMcpTools } from '../domain/platform-mcp-tools.js';
|
|
3
2
|
export { StacklinkMemoryLifecycle, StacklinkMemoryLifecycleError, createStacklinkMemoryLifecycle, serializeCompletedConversationTurn, } from './memory-lifecycle.js';
|
|
4
|
-
export const
|
|
5
|
-
export const stacklinkTypeScriptSdkVersion = '0.2.0';
|
|
3
|
+
export const stacklinkTypeScriptSdkVersion = '0.2.2';
|
|
6
4
|
const workbenchReusableRunComparisonWaitMs = 120_000;
|
|
7
5
|
const workbenchReusableRunComparisonPollMs = 1_000;
|
|
8
6
|
export class StacklinkApiError extends Error {
|
|
@@ -17,6 +15,18 @@ export class StacklinkApiError extends Error {
|
|
|
17
15
|
this.name = 'StacklinkApiError';
|
|
18
16
|
}
|
|
19
17
|
}
|
|
18
|
+
function providerCatalogSdkQuery(input) {
|
|
19
|
+
return {
|
|
20
|
+
limit: input.limit,
|
|
21
|
+
cursor: input.cursor,
|
|
22
|
+
search: input.search,
|
|
23
|
+
providerKey: input.providerKeys,
|
|
24
|
+
categoryKey: input.categoryKeys,
|
|
25
|
+
projectStatus: input.projectStatus,
|
|
26
|
+
connectionStatus: input.connectionStatus,
|
|
27
|
+
tenantId: input.tenantId,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
20
30
|
function withoutCacheControl(input) {
|
|
21
31
|
const { cache: _cache, ...rest } = input;
|
|
22
32
|
return rest;
|
|
@@ -35,8 +45,69 @@ function stableSdkStringify(value) {
|
|
|
35
45
|
const record = value;
|
|
36
46
|
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableSdkStringify(record[key])}`).join(',')}}`;
|
|
37
47
|
}
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
const sdkDiscoveryMcpMethods = new Set([
|
|
49
|
+
'initialize',
|
|
50
|
+
'tools/list',
|
|
51
|
+
'resources/list',
|
|
52
|
+
'resources/templates/list',
|
|
53
|
+
'prompts/list',
|
|
54
|
+
]);
|
|
55
|
+
let sdkRequestSequence = 0;
|
|
56
|
+
function mcpRequestIsDiscovery(input) {
|
|
57
|
+
const requests = Array.isArray(input) ? input : [input];
|
|
58
|
+
return requests.length > 0 && requests.every((request) => (typeof request.method === 'string' && sdkDiscoveryMcpMethods.has(request.method)));
|
|
59
|
+
}
|
|
60
|
+
function positiveSdkInteger(value, field) {
|
|
61
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
62
|
+
throw new Error(`Stacklink ${field} must be a positive integer.`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function sdkString(value) {
|
|
67
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
68
|
+
}
|
|
69
|
+
function createSdkRequestId() {
|
|
70
|
+
const randomUuid = globalThis.crypto?.randomUUID?.();
|
|
71
|
+
if (randomUuid)
|
|
72
|
+
return randomUuid;
|
|
73
|
+
sdkRequestSequence += 1;
|
|
74
|
+
return `stacklink-${Date.now()}-${sdkRequestSequence}`;
|
|
75
|
+
}
|
|
76
|
+
function sdkRetrySafe(method, idempotencyKey) {
|
|
77
|
+
return method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || Boolean(idempotencyKey);
|
|
78
|
+
}
|
|
79
|
+
function sdkRetryableStatus(status) {
|
|
80
|
+
return status === 408 || status === 429 || status === 502 || status === 503 || status === 504;
|
|
81
|
+
}
|
|
82
|
+
function sdkRetryDelayMs(response, attempt) {
|
|
83
|
+
const retryAfter = response?.headers.get('retry-after');
|
|
84
|
+
if (retryAfter) {
|
|
85
|
+
const seconds = Number(retryAfter);
|
|
86
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
87
|
+
return Math.ceil(seconds * 1000);
|
|
88
|
+
const at = Date.parse(retryAfter);
|
|
89
|
+
if (Number.isFinite(at))
|
|
90
|
+
return Math.max(0, at - Date.now());
|
|
91
|
+
}
|
|
92
|
+
return Math.min(1_000, 100 * (2 ** Math.max(0, attempt - 1)));
|
|
93
|
+
}
|
|
94
|
+
function sdkDelay(ms, signal) {
|
|
95
|
+
if (signal.aborted)
|
|
96
|
+
return Promise.reject(signal.reason ?? new Error('Stacklink request was aborted.'));
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const timeout = setTimeout(() => {
|
|
99
|
+
signal.removeEventListener('abort', onAbort);
|
|
100
|
+
resolve();
|
|
101
|
+
}, ms);
|
|
102
|
+
const onAbort = () => {
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
reject(signal.reason ?? new Error('Stacklink request was aborted.'));
|
|
105
|
+
};
|
|
106
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function sdkTimeoutError(timeoutMs, requestId) {
|
|
110
|
+
return new StacklinkApiError(`Stacklink request exceeded its ${timeoutMs} ms deadline.`, 408, 'sdk_request_timeout', { requestId, timeoutMs });
|
|
40
111
|
}
|
|
41
112
|
function normalizeKnowledgePackSelection(value) {
|
|
42
113
|
if (Array.isArray(value)) {
|
|
@@ -120,6 +191,9 @@ export class Stacklink {
|
|
|
120
191
|
fetchImpl;
|
|
121
192
|
cacheOptions;
|
|
122
193
|
responseCache = new Map();
|
|
194
|
+
discoveryRequests = new Map();
|
|
195
|
+
timeoutMs;
|
|
196
|
+
maxAttempts;
|
|
123
197
|
webhooks;
|
|
124
198
|
triggers;
|
|
125
199
|
sessions;
|
|
@@ -136,6 +210,8 @@ export class Stacklink {
|
|
|
136
210
|
this.registrationKey = options.registrationKey;
|
|
137
211
|
this.baseUrl = options.baseUrl.endsWith('/') ? options.baseUrl.slice(0, -1) : options.baseUrl;
|
|
138
212
|
this.fetchImpl = options.fetch ?? fetch;
|
|
213
|
+
this.timeoutMs = positiveSdkInteger(options.timeoutMs ?? 15_000, 'timeoutMs');
|
|
214
|
+
this.maxAttempts = Math.min(3, positiveSdkInteger(options.maxAttempts ?? 3, 'maxAttempts'));
|
|
139
215
|
this.cacheOptions = {
|
|
140
216
|
enabled: options.cache?.enabled ?? true,
|
|
141
217
|
toolsListTtlMs: options.cache?.toolsListTtlMs ?? 300_000,
|
|
@@ -176,27 +252,24 @@ export class Stacklink {
|
|
|
176
252
|
userEmail: input.userEmail,
|
|
177
253
|
scopes: input.scopes ?? [],
|
|
178
254
|
toolRouter: input.toolRouter,
|
|
179
|
-
|
|
180
|
-
preload: input.preload,
|
|
255
|
+
modelSurface: input.modelSurface,
|
|
181
256
|
toolkits: input.toolkits,
|
|
182
257
|
tools: input.tools,
|
|
183
258
|
authConfigs: input.authConfigs,
|
|
184
259
|
connectedAccounts: input.connectedAccounts,
|
|
260
|
+
providerMutationMode: input.providerMutationMode,
|
|
185
261
|
multiAccount: input.multiAccount,
|
|
186
262
|
workbench: input.workbench,
|
|
187
263
|
knowledgePacks: input.knowledgePacks,
|
|
188
264
|
memory: input.memory,
|
|
189
|
-
metaTools: input.metaTools,
|
|
190
265
|
metadata: input.metadata ?? {},
|
|
191
266
|
},
|
|
192
267
|
});
|
|
193
|
-
|
|
194
|
-
return new ToolRouterSession(this, response.session.id, response.session.tenantId, response.session.mcp, projection.contractVersion, projection.tools);
|
|
268
|
+
return new ToolRouterSession(this, response.session.id, response.session.tenantId, response.session.mcp, response.metaTools.contractVersion, response.metaTools.tools);
|
|
195
269
|
}
|
|
196
270
|
async use(sessionId) {
|
|
197
|
-
const response = await this.
|
|
198
|
-
|
|
199
|
-
return new ToolRouterSession(this, response.session.id, response.session.tenantId, response.session.mcp, projection.contractVersion, projection.tools);
|
|
271
|
+
const response = await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}/bootstrap`);
|
|
272
|
+
return new ToolRouterSession(this, response.session.id, response.session.tenantId, response.session.mcp, response.metaTools.contractVersion, response.metaTools.tools);
|
|
200
273
|
}
|
|
201
274
|
clearCache(prefix) {
|
|
202
275
|
if (!prefix) {
|
|
@@ -262,19 +335,24 @@ export class Stacklink {
|
|
|
262
335
|
method: 'DELETE',
|
|
263
336
|
});
|
|
264
337
|
}
|
|
265
|
-
async listProviders() {
|
|
266
|
-
return this.request('/v1/providers'
|
|
338
|
+
async listProviders(query = {}) {
|
|
339
|
+
return this.request('/v1/providers', {
|
|
340
|
+
query: providerCatalogSdkQuery(query),
|
|
341
|
+
});
|
|
267
342
|
}
|
|
268
343
|
async listProjectProviders(query = {}) {
|
|
269
344
|
return this.request('/v1/project/providers', {
|
|
270
|
-
query:
|
|
345
|
+
query: providerCatalogSdkQuery(query),
|
|
271
346
|
});
|
|
272
347
|
}
|
|
273
348
|
async listProviderTools(providerKey, input = {}) {
|
|
274
349
|
return this.request(`/v1/providers/${providerKey}/tools`, {
|
|
275
350
|
query: {
|
|
351
|
+
limit: input.limit,
|
|
352
|
+
cursor: input.cursor,
|
|
353
|
+
search: input.search,
|
|
354
|
+
capability: input.capability,
|
|
276
355
|
includePlanned: input.includePlanned === true ? 'true' : undefined,
|
|
277
|
-
responseMode: input.responseMode === 'compact' ? 'compact' : undefined,
|
|
278
356
|
},
|
|
279
357
|
});
|
|
280
358
|
}
|
|
@@ -360,6 +438,10 @@ export class Stacklink {
|
|
|
360
438
|
method: 'POST',
|
|
361
439
|
body: input,
|
|
362
440
|
signal: options.signal,
|
|
441
|
+
timeoutMs: options.timeoutMs ?? 60_000,
|
|
442
|
+
idempotencyKey: options.idempotencyKey ?? sdkString(input.idempotencyKey),
|
|
443
|
+
requestId: options.requestId,
|
|
444
|
+
traceparent: options.traceparent,
|
|
363
445
|
});
|
|
364
446
|
}
|
|
365
447
|
async executeToolsBatch(input, options = {}) {
|
|
@@ -367,6 +449,10 @@ export class Stacklink {
|
|
|
367
449
|
method: 'POST',
|
|
368
450
|
body: input,
|
|
369
451
|
signal: options.signal,
|
|
452
|
+
timeoutMs: options.timeoutMs ?? 60_000,
|
|
453
|
+
idempotencyKey: options.idempotencyKey ?? sdkString(input.idempotencyKey),
|
|
454
|
+
requestId: options.requestId,
|
|
455
|
+
traceparent: options.traceparent,
|
|
370
456
|
});
|
|
371
457
|
}
|
|
372
458
|
async getSubscription() {
|
|
@@ -619,7 +705,6 @@ export class Stacklink {
|
|
|
619
705
|
executionKind,
|
|
620
706
|
executionId,
|
|
621
707
|
waitTimeoutMs: 10_000,
|
|
622
|
-
pollIntervalMs: 500,
|
|
623
708
|
}, options);
|
|
624
709
|
const execution = isSdkRecord(waited.execution) ? waited.execution : undefined;
|
|
625
710
|
if (execution && isTerminalSdkExecution(execution)) {
|
|
@@ -660,15 +745,60 @@ export class Stacklink {
|
|
|
660
745
|
signal: options.signal,
|
|
661
746
|
});
|
|
662
747
|
}
|
|
748
|
+
async *streamExecutionEvents(input, options = {}) {
|
|
749
|
+
let afterSequence = input.afterSequence === undefined ? undefined : String(input.afterSequence);
|
|
750
|
+
while (!options.signal?.aborted) {
|
|
751
|
+
const response = await this.requestRaw(`/v1/executions/${input.executionKind}/${input.executionId}/events/stream`, {
|
|
752
|
+
headers: {
|
|
753
|
+
accept: 'text/event-stream',
|
|
754
|
+
...(afterSequence === undefined ? {} : { 'Last-Event-ID': afterSequence }),
|
|
755
|
+
},
|
|
756
|
+
query: {
|
|
757
|
+
sessionId: input.sessionId,
|
|
758
|
+
afterSequence,
|
|
759
|
+
limit: input.limit,
|
|
760
|
+
},
|
|
761
|
+
signal: options.signal,
|
|
762
|
+
timeoutMs: options.timeoutMs ?? 30_000,
|
|
763
|
+
});
|
|
764
|
+
try {
|
|
765
|
+
for await (const payload of this.readJsonEventStream(response)) {
|
|
766
|
+
if (!isSdkRecord(payload))
|
|
767
|
+
continue;
|
|
768
|
+
const events = Array.isArray(payload.events) ? payload.events : [];
|
|
769
|
+
for (const event of events) {
|
|
770
|
+
if (isSdkRecord(event) && (typeof event.sequence === 'string' || typeof event.sequence === 'number')) {
|
|
771
|
+
afterSequence = String(event.sequence);
|
|
772
|
+
}
|
|
773
|
+
yield event;
|
|
774
|
+
}
|
|
775
|
+
if (typeof payload.nextCursor === 'string')
|
|
776
|
+
afterSequence = payload.nextCursor;
|
|
777
|
+
if (payload.terminal === true)
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
if (options.signal?.aborted)
|
|
783
|
+
throw options.signal.reason ?? error;
|
|
784
|
+
const errorName = error instanceof Error ? error.name : '';
|
|
785
|
+
if (errorName !== 'AbortError' && errorName !== 'TimeoutError')
|
|
786
|
+
throw error;
|
|
787
|
+
// The per-connection deadline bounds a stream. Reconnect from the durable cursor.
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (options.signal?.aborted)
|
|
791
|
+
throw options.signal.reason ?? new Error('Stacklink execution stream was aborted.');
|
|
792
|
+
}
|
|
663
793
|
async waitForExecution(input, options = {}) {
|
|
664
794
|
return this.request(`/v1/executions/${input.executionKind}/${input.executionId}/wait`, {
|
|
665
795
|
method: 'POST',
|
|
666
796
|
body: {
|
|
667
797
|
sessionId: input.sessionId,
|
|
668
798
|
waitTimeoutMs: input.waitTimeoutMs,
|
|
669
|
-
pollIntervalMs: input.pollIntervalMs,
|
|
670
799
|
},
|
|
671
|
-
|
|
800
|
+
...options,
|
|
801
|
+
timeoutMs: options.timeoutMs ?? (input.waitTimeoutMs ?? 30_000) + 5_000,
|
|
672
802
|
});
|
|
673
803
|
}
|
|
674
804
|
async cancelExecution(input, options = {}) {
|
|
@@ -719,7 +849,7 @@ export class Stacklink {
|
|
|
719
849
|
async getConnectedAccount(accountId) {
|
|
720
850
|
return this.request(`/v1/connections/${accountId}`);
|
|
721
851
|
}
|
|
722
|
-
async listConnectedAccountTools(accountId, input
|
|
852
|
+
async listConnectedAccountTools(accountId, input) {
|
|
723
853
|
return this.request(`/v1/connections/${accountId}/tools`, {
|
|
724
854
|
query: {
|
|
725
855
|
...(input.includePlanned !== undefined ? { includePlanned: String(input.includePlanned) } : {}),
|
|
@@ -985,21 +1115,35 @@ export class Stacklink {
|
|
|
985
1115
|
method: 'DELETE',
|
|
986
1116
|
});
|
|
987
1117
|
}
|
|
988
|
-
async mcpRequest(input, options = {}) {
|
|
989
|
-
return this.mcpRequestAtPath('/v1/mcp', input, options);
|
|
990
|
-
}
|
|
991
1118
|
async mcpSessionRequest(sessionId, input, options = {}) {
|
|
992
1119
|
return this.mcpRequestAtPath(`/v1/sessions/${encodeURIComponent(sessionId)}/mcp`, input, options);
|
|
993
1120
|
}
|
|
994
1121
|
async mcpRequestAtPath(path, input, options = {}) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1122
|
+
if (!Array.isArray(input))
|
|
1123
|
+
return this.mcpRequestStreamAtPath(path, input, {
|
|
1124
|
+
...options, timeoutMs: options.timeoutMs ?? (mcpRequestIsDiscovery(input) ? this.timeoutMs : 60_000),
|
|
1125
|
+
});
|
|
1126
|
+
const response = await this.requestRaw(path, { method: 'POST', body: input, headers: { accept: 'application/json, text/event-stream' },
|
|
1127
|
+
...options, timeoutMs: options.timeoutMs ?? 60_000 });
|
|
1128
|
+
if (response.status === 202)
|
|
1129
|
+
return null;
|
|
1130
|
+
const value = await response.json();
|
|
1131
|
+
if (!Array.isArray(value))
|
|
1132
|
+
throw new StacklinkApiError('Expected MCP batch responses.', 502, 'mcp_response_invalid');
|
|
1133
|
+
const expected = input.filter((entry) => entry.id !== undefined && entry.id !== null);
|
|
1134
|
+
if (expected.length !== value.length)
|
|
1135
|
+
throw new StacklinkApiError('MCP batch response count does not match.', 502, 'mcp_response_invalid');
|
|
1136
|
+
const seen = new Set();
|
|
1137
|
+
for (const message of value) {
|
|
1138
|
+
if (!isSdkRecord(message) || seen.has(message.id))
|
|
1139
|
+
throw new StacklinkApiError('Duplicate or invalid MCP response.', 502, 'mcp_response_invalid');
|
|
1140
|
+
const request = expected.find((entry) => entry.id === message.id);
|
|
1141
|
+
if (!request)
|
|
1142
|
+
throw new StacklinkApiError('MCP response belongs to another request.', 502, 'mcp_response_invalid');
|
|
1143
|
+
validateSdkMcpResponse(message, request.id);
|
|
1144
|
+
seen.add(message.id);
|
|
1145
|
+
}
|
|
1146
|
+
return value;
|
|
1003
1147
|
}
|
|
1004
1148
|
async mcpSessionRequestStream(sessionId, input, options = {}) {
|
|
1005
1149
|
return this.mcpRequestStreamAtPath(`/v1/sessions/${encodeURIComponent(sessionId)}/mcp`, input, options);
|
|
@@ -1032,49 +1176,40 @@ export class Stacklink {
|
|
|
1032
1176
|
method: 'POST',
|
|
1033
1177
|
body: input,
|
|
1034
1178
|
signal: options.signal,
|
|
1179
|
+
timeoutMs: options.timeoutMs ?? 60_000,
|
|
1180
|
+
idempotencyKey: options.idempotencyKey,
|
|
1181
|
+
requestId: options.requestId,
|
|
1182
|
+
traceparent: options.traceparent,
|
|
1035
1183
|
headers: {
|
|
1036
1184
|
accept: 'text/event-stream, application/json',
|
|
1037
1185
|
},
|
|
1038
1186
|
});
|
|
1039
1187
|
const contentType = response.headers.get('content-type') ?? '';
|
|
1188
|
+
if (response.status === 202 && (input.id === undefined || input.id === null))
|
|
1189
|
+
return null;
|
|
1040
1190
|
if (!contentType.includes('text/event-stream')) {
|
|
1041
|
-
|
|
1191
|
+
const value = await response.json();
|
|
1192
|
+
validateSdkMcpResponse(value, input.id);
|
|
1193
|
+
return value;
|
|
1042
1194
|
}
|
|
1043
|
-
return await this.readMcpEventStream(response, options.onProgress);
|
|
1195
|
+
return await this.readMcpEventStream(response, input, options.onProgress);
|
|
1044
1196
|
}
|
|
1045
1197
|
finally {
|
|
1046
1198
|
completed = true;
|
|
1047
1199
|
options.signal?.removeEventListener('abort', cancel);
|
|
1048
1200
|
}
|
|
1049
1201
|
}
|
|
1050
|
-
async
|
|
1051
|
-
return this.
|
|
1052
|
-
jsonrpc: '2.0',
|
|
1053
|
-
id: 'tools-list',
|
|
1054
|
-
method: 'tools/list',
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
async callMcpTool(name, args) {
|
|
1058
|
-
return this.mcpRequest({
|
|
1202
|
+
async callSessionMcpTool(sessionId, name, args, options = {}) {
|
|
1203
|
+
return this.mcpSessionRequestStream(sessionId, {
|
|
1059
1204
|
jsonrpc: '2.0',
|
|
1060
|
-
id:
|
|
1205
|
+
id: createSdkRequestId(),
|
|
1061
1206
|
method: 'tools/call',
|
|
1062
1207
|
params: {
|
|
1063
1208
|
name,
|
|
1209
|
+
...(options.onProgress ? { _meta: { progressToken: createSdkRequestId() } } : {}),
|
|
1064
1210
|
arguments: args,
|
|
1065
1211
|
},
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
async callSessionMcpTool(sessionId, name, args) {
|
|
1069
|
-
return this.mcpSessionRequest(sessionId, {
|
|
1070
|
-
jsonrpc: '2.0',
|
|
1071
|
-
id: `${name}-call`,
|
|
1072
|
-
method: 'tools/call',
|
|
1073
|
-
params: {
|
|
1074
|
-
name,
|
|
1075
|
-
arguments: args,
|
|
1076
|
-
},
|
|
1077
|
-
});
|
|
1212
|
+
}, options);
|
|
1078
1213
|
}
|
|
1079
1214
|
async listKnowledgePacks() {
|
|
1080
1215
|
return this.request('/v1/knowledge/packs');
|
|
@@ -1221,26 +1356,17 @@ export class Stacklink {
|
|
|
1221
1356
|
signal.addEventListener('abort', cancel, { once: true });
|
|
1222
1357
|
}
|
|
1223
1358
|
async request(path, init = {}) {
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1359
|
+
const response = await this.performRequest(path, init);
|
|
1360
|
+
const responseText = await response.text();
|
|
1361
|
+
let body = null;
|
|
1362
|
+
if (responseText.length > 0) {
|
|
1363
|
+
try {
|
|
1364
|
+
body = JSON.parse(responseText);
|
|
1231
1365
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1366
|
+
catch {
|
|
1367
|
+
body = null;
|
|
1234
1368
|
}
|
|
1235
1369
|
}
|
|
1236
|
-
const headers = this.requestHeaders(init.registration === true, init.headers);
|
|
1237
|
-
const response = await this.fetchImpl(url, {
|
|
1238
|
-
method: init.method ?? 'GET',
|
|
1239
|
-
headers,
|
|
1240
|
-
...(init.signal ? { signal: init.signal } : {}),
|
|
1241
|
-
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
1242
|
-
});
|
|
1243
|
-
const body = await response.json().catch(() => null);
|
|
1244
1370
|
if (!response.ok) {
|
|
1245
1371
|
const message = body && typeof body === 'object' && 'message' in body
|
|
1246
1372
|
? String(body.message)
|
|
@@ -1256,6 +1382,21 @@ export class Stacklink {
|
|
|
1256
1382
|
return body;
|
|
1257
1383
|
}
|
|
1258
1384
|
async requestRaw(path, init = {}) {
|
|
1385
|
+
const response = await this.performRequest(path, init);
|
|
1386
|
+
if (!response.ok) {
|
|
1387
|
+
const body = await response.text();
|
|
1388
|
+
let parsed;
|
|
1389
|
+
try {
|
|
1390
|
+
parsed = JSON.parse(body);
|
|
1391
|
+
}
|
|
1392
|
+
catch {
|
|
1393
|
+
parsed = null;
|
|
1394
|
+
}
|
|
1395
|
+
throw new StacklinkApiError(isSdkRecord(parsed) && typeof parsed.message === 'string' ? parsed.message : `Stacklink request failed with ${response.status}`, response.status, isSdkRecord(parsed) && typeof parsed.code === 'string' ? parsed.code : undefined, isSdkRecord(parsed) ? parsed.details : undefined);
|
|
1396
|
+
}
|
|
1397
|
+
return response;
|
|
1398
|
+
}
|
|
1399
|
+
async performRequest(path, init) {
|
|
1259
1400
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
1260
1401
|
for (const [key, value] of Object.entries(init.query ?? {})) {
|
|
1261
1402
|
if (Array.isArray(value)) {
|
|
@@ -1268,17 +1409,55 @@ export class Stacklink {
|
|
|
1268
1409
|
url.searchParams.set(key, String(value));
|
|
1269
1410
|
}
|
|
1270
1411
|
}
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1412
|
+
const method = init.method ?? 'GET';
|
|
1413
|
+
const timeoutMs = positiveSdkInteger(init.timeoutMs ?? this.timeoutMs, 'timeoutMs');
|
|
1414
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
1415
|
+
const requestId = init.requestId ?? createSdkRequestId();
|
|
1416
|
+
const retrySafe = !path.endsWith('/mcp') && sdkRetrySafe(method, init.idempotencyKey);
|
|
1417
|
+
let attempt = 0;
|
|
1418
|
+
while (true) {
|
|
1419
|
+
attempt += 1;
|
|
1420
|
+
const remainingMs = deadlineAt - Date.now();
|
|
1421
|
+
if (remainingMs <= 0)
|
|
1422
|
+
throw sdkTimeoutError(timeoutMs, requestId);
|
|
1423
|
+
const deadlineSignal = AbortSignal.timeout(remainingMs);
|
|
1424
|
+
const signal = init.signal ? AbortSignal.any([init.signal, deadlineSignal]) : deadlineSignal;
|
|
1425
|
+
const headers = this.requestHeaders(init.registration === true, {
|
|
1426
|
+
...init.headers,
|
|
1427
|
+
'X-Stacklink-Client-Request-Id': requestId,
|
|
1428
|
+
'X-Stacklink-Timeout-Ms': String(remainingMs),
|
|
1429
|
+
...(init.idempotencyKey ? { 'Idempotency-Key': init.idempotencyKey } : {}),
|
|
1430
|
+
...(init.traceparent ? { traceparent: init.traceparent } : {}),
|
|
1431
|
+
});
|
|
1432
|
+
try {
|
|
1433
|
+
const response = await this.fetchImpl(url, {
|
|
1434
|
+
method,
|
|
1435
|
+
headers,
|
|
1436
|
+
signal,
|
|
1437
|
+
...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
|
|
1438
|
+
});
|
|
1439
|
+
if (!retrySafe || attempt >= this.maxAttempts || !sdkRetryableStatus(response.status)) {
|
|
1440
|
+
return response;
|
|
1441
|
+
}
|
|
1442
|
+
const delayMs = sdkRetryDelayMs(response, attempt);
|
|
1443
|
+
if (delayMs >= deadlineAt - Date.now())
|
|
1444
|
+
return response;
|
|
1445
|
+
await response.body?.cancel().catch(() => undefined);
|
|
1446
|
+
await sdkDelay(delayMs, signal);
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
if (init.signal?.aborted)
|
|
1450
|
+
throw init.signal.reason ?? error;
|
|
1451
|
+
if (deadlineSignal.aborted || Date.now() >= deadlineAt)
|
|
1452
|
+
throw sdkTimeoutError(timeoutMs, requestId);
|
|
1453
|
+
if (!retrySafe || attempt >= this.maxAttempts)
|
|
1454
|
+
throw error;
|
|
1455
|
+
const delayMs = sdkRetryDelayMs(undefined, attempt);
|
|
1456
|
+
if (delayMs >= deadlineAt - Date.now())
|
|
1457
|
+
throw error;
|
|
1458
|
+
await sdkDelay(delayMs, signal);
|
|
1459
|
+
}
|
|
1280
1460
|
}
|
|
1281
|
-
return response;
|
|
1282
1461
|
}
|
|
1283
1462
|
async cachedDiscoveryRequest(cacheKey, ttlMs, control, producer) {
|
|
1284
1463
|
if (!this.cacheOptions.enabled || control?.cache === 'bypass')
|
|
@@ -1287,13 +1466,21 @@ export class Stacklink {
|
|
|
1287
1466
|
const existing = this.responseCache.get(cacheKey);
|
|
1288
1467
|
if (existing && existing.expiresAt > now)
|
|
1289
1468
|
return existing.value;
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1469
|
+
const active = this.discoveryRequests.get(cacheKey);
|
|
1470
|
+
if (active)
|
|
1471
|
+
return active;
|
|
1472
|
+
const request = producer().then((value) => {
|
|
1473
|
+
this.responseCache.set(cacheKey, {
|
|
1474
|
+
value,
|
|
1475
|
+
expiresAt: Date.now() + ttlMs,
|
|
1476
|
+
});
|
|
1477
|
+
this.trimResponseCache();
|
|
1478
|
+
return value;
|
|
1479
|
+
}).finally(() => {
|
|
1480
|
+
this.discoveryRequests.delete(cacheKey);
|
|
1294
1481
|
});
|
|
1295
|
-
this.
|
|
1296
|
-
return
|
|
1482
|
+
this.discoveryRequests.set(cacheKey, request);
|
|
1483
|
+
return request;
|
|
1297
1484
|
}
|
|
1298
1485
|
discoveryCacheTtl(kind) {
|
|
1299
1486
|
if (kind === 'toolsList')
|
|
@@ -1313,7 +1500,7 @@ export class Stacklink {
|
|
|
1313
1500
|
this.responseCache.delete(firstKey);
|
|
1314
1501
|
}
|
|
1315
1502
|
}
|
|
1316
|
-
async readMcpEventStream(response, onProgress) {
|
|
1503
|
+
async readMcpEventStream(response, request, onProgress) {
|
|
1317
1504
|
if (!response.body)
|
|
1318
1505
|
throw new Error('Stacklink MCP stream response has no body.');
|
|
1319
1506
|
const reader = response.body.getReader();
|
|
@@ -1321,6 +1508,10 @@ export class Stacklink {
|
|
|
1321
1508
|
let buffer = '';
|
|
1322
1509
|
let dataLines = [];
|
|
1323
1510
|
let finalResponse;
|
|
1511
|
+
let bytes = 0;
|
|
1512
|
+
const requestParams = isSdkRecord(request.params) ? request.params : {};
|
|
1513
|
+
const meta = isSdkRecord(requestParams._meta) ? requestParams._meta : {};
|
|
1514
|
+
const progressToken = meta.progressToken;
|
|
1324
1515
|
const handleLine = (line) => {
|
|
1325
1516
|
if (line.endsWith('\r'))
|
|
1326
1517
|
line = line.slice(0, -1);
|
|
@@ -1330,10 +1521,24 @@ export class Stacklink {
|
|
|
1330
1521
|
dataLines = [];
|
|
1331
1522
|
if (data.length > 0) {
|
|
1332
1523
|
const message = JSON.parse(data);
|
|
1524
|
+
if (!isSdkRecord(message) || message.jsonrpc !== '2.0') {
|
|
1525
|
+
throw new StacklinkApiError('Stacklink returned an invalid MCP message.', 502, 'mcp_response_invalid');
|
|
1526
|
+
}
|
|
1333
1527
|
if (isSdkRecord(message) && message.method === 'notifications/progress' && isSdkRecord(message.params)) {
|
|
1334
|
-
|
|
1528
|
+
if (progressToken !== undefined && message.params.progressToken === progressToken) {
|
|
1529
|
+
onProgress?.(message.params);
|
|
1530
|
+
}
|
|
1335
1531
|
}
|
|
1336
|
-
else if (
|
|
1532
|
+
else if (typeof message.method === 'string' && message.id !== undefined) {
|
|
1533
|
+
throw new StacklinkApiError('The server requested a capability this SDK did not declare.', 502, 'mcp_capability_not_supported');
|
|
1534
|
+
}
|
|
1535
|
+
else if ('result' in message || 'error' in message) {
|
|
1536
|
+
if (message.id !== request.id)
|
|
1537
|
+
return;
|
|
1538
|
+
if (finalResponse !== undefined || ('result' in message) === ('error' in message)) {
|
|
1539
|
+
throw new StacklinkApiError('Stacklink returned a conflicting MCP result.', 502, 'mcp_response_invalid');
|
|
1540
|
+
}
|
|
1541
|
+
validateSdkMcpResponse(message, request.id);
|
|
1337
1542
|
finalResponse = message;
|
|
1338
1543
|
}
|
|
1339
1544
|
}
|
|
@@ -1347,6 +1552,63 @@ export class Stacklink {
|
|
|
1347
1552
|
dataLines.push(value);
|
|
1348
1553
|
}
|
|
1349
1554
|
};
|
|
1555
|
+
try {
|
|
1556
|
+
while (true) {
|
|
1557
|
+
const read = await reader.read();
|
|
1558
|
+
if (read.done)
|
|
1559
|
+
break;
|
|
1560
|
+
bytes += read.value.byteLength;
|
|
1561
|
+
if (bytes > 8 * 1024 * 1024)
|
|
1562
|
+
throw new StacklinkApiError('MCP response exceeds 8 MiB.', 502, 'mcp_response_too_large');
|
|
1563
|
+
buffer += decoder.decode(read.value, { stream: true });
|
|
1564
|
+
let index = buffer.indexOf('\n');
|
|
1565
|
+
while (index >= 0) {
|
|
1566
|
+
handleLine(buffer.slice(0, index));
|
|
1567
|
+
buffer = buffer.slice(index + 1);
|
|
1568
|
+
if (finalResponse !== undefined)
|
|
1569
|
+
return finalResponse;
|
|
1570
|
+
index = buffer.indexOf('\n');
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
buffer += decoder.decode();
|
|
1574
|
+
if (buffer.length > 0)
|
|
1575
|
+
handleLine(buffer);
|
|
1576
|
+
handleLine('');
|
|
1577
|
+
if (finalResponse === undefined)
|
|
1578
|
+
throw new Error('Stacklink MCP stream ended without a final JSON-RPC response.');
|
|
1579
|
+
return finalResponse;
|
|
1580
|
+
}
|
|
1581
|
+
finally {
|
|
1582
|
+
await reader.cancel().catch(() => undefined);
|
|
1583
|
+
reader.releaseLock();
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async *readJsonEventStream(response) {
|
|
1587
|
+
if (!response.body)
|
|
1588
|
+
throw new Error('Stacklink event stream response has no body.');
|
|
1589
|
+
const reader = response.body.getReader();
|
|
1590
|
+
const decoder = new TextDecoder();
|
|
1591
|
+
let buffer = '';
|
|
1592
|
+
let dataLines = [];
|
|
1593
|
+
const payloadForLine = (lineValue) => {
|
|
1594
|
+
let line = lineValue;
|
|
1595
|
+
if (line.endsWith('\r'))
|
|
1596
|
+
line = line.slice(0, -1);
|
|
1597
|
+
if (line === '') {
|
|
1598
|
+
if (dataLines.length === 0)
|
|
1599
|
+
return undefined;
|
|
1600
|
+
const data = dataLines.join('\n');
|
|
1601
|
+
dataLines = [];
|
|
1602
|
+
return data.length === 0 ? undefined : JSON.parse(data);
|
|
1603
|
+
}
|
|
1604
|
+
if (line.startsWith('data:')) {
|
|
1605
|
+
let value = line.slice(5);
|
|
1606
|
+
if (value.startsWith(' '))
|
|
1607
|
+
value = value.slice(1);
|
|
1608
|
+
dataLines.push(value);
|
|
1609
|
+
}
|
|
1610
|
+
return undefined;
|
|
1611
|
+
};
|
|
1350
1612
|
while (true) {
|
|
1351
1613
|
const read = await reader.read();
|
|
1352
1614
|
if (read.done)
|
|
@@ -1354,18 +1616,22 @@ export class Stacklink {
|
|
|
1354
1616
|
buffer += decoder.decode(read.value, { stream: true });
|
|
1355
1617
|
let index = buffer.indexOf('\n');
|
|
1356
1618
|
while (index >= 0) {
|
|
1357
|
-
|
|
1619
|
+
const payload = payloadForLine(buffer.slice(0, index));
|
|
1620
|
+
if (payload !== undefined)
|
|
1621
|
+
yield payload;
|
|
1358
1622
|
buffer = buffer.slice(index + 1);
|
|
1359
1623
|
index = buffer.indexOf('\n');
|
|
1360
1624
|
}
|
|
1361
1625
|
}
|
|
1362
1626
|
buffer += decoder.decode();
|
|
1363
|
-
if (buffer.length > 0)
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1627
|
+
if (buffer.length > 0) {
|
|
1628
|
+
const payload = payloadForLine(buffer);
|
|
1629
|
+
if (payload !== undefined)
|
|
1630
|
+
yield payload;
|
|
1631
|
+
}
|
|
1632
|
+
const finalPayload = payloadForLine('');
|
|
1633
|
+
if (finalPayload !== undefined)
|
|
1634
|
+
yield finalPayload;
|
|
1369
1635
|
}
|
|
1370
1636
|
requestHeaders(registration, extraHeaders = {}) {
|
|
1371
1637
|
if (registration) {
|
|
@@ -1540,6 +1806,12 @@ export class StacklinkMemoryClient {
|
|
|
1540
1806
|
body: input,
|
|
1541
1807
|
});
|
|
1542
1808
|
}
|
|
1809
|
+
reviewQuality(memoryId, input) {
|
|
1810
|
+
return this.client.request(`/v1/memory/records/${memoryId}/quality-review`, {
|
|
1811
|
+
method: 'POST',
|
|
1812
|
+
body: input,
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1543
1815
|
forget(memoryId, input) {
|
|
1544
1816
|
return this.client.request(`/v1/memory/records/${memoryId}/forget`, {
|
|
1545
1817
|
method: 'POST',
|
|
@@ -1629,8 +1901,8 @@ export class StacklinkMemoryClient {
|
|
|
1629
1901
|
body: input,
|
|
1630
1902
|
});
|
|
1631
1903
|
}
|
|
1632
|
-
deleteSource(bindingId) {
|
|
1633
|
-
return this.client.request(`/v1/memory/sources/${bindingId}`, { method: 'DELETE' });
|
|
1904
|
+
deleteSource(bindingId, input) {
|
|
1905
|
+
return this.client.request(`/v1/memory/sources/${bindingId}`, { method: 'DELETE', body: input });
|
|
1634
1906
|
}
|
|
1635
1907
|
replaySourceItem(bindingId, itemId) {
|
|
1636
1908
|
return this.client.request(`/v1/memory/sources/${bindingId}/items/${itemId}/replay`, {
|
|
@@ -1688,8 +1960,10 @@ export class StacklinkSessionsClient {
|
|
|
1688
1960
|
metaTools(sessionId) {
|
|
1689
1961
|
return this.client.getSessionMetaTools(sessionId);
|
|
1690
1962
|
}
|
|
1691
|
-
toolkits(sessionId) {
|
|
1692
|
-
return this.client.request(`/v1/sessions/${sessionId}/toolkits
|
|
1963
|
+
toolkits(sessionId, query = {}) {
|
|
1964
|
+
return this.client.request(`/v1/sessions/${sessionId}/toolkits`, {
|
|
1965
|
+
query: providerCatalogSdkQuery(query),
|
|
1966
|
+
});
|
|
1693
1967
|
}
|
|
1694
1968
|
streamEvents(sessionId, input = {}) {
|
|
1695
1969
|
return this.client.requestRaw(`/v1/sessions/${sessionId}/events/stream`, {
|
|
@@ -1700,7 +1974,6 @@ export class StacklinkSessionsClient {
|
|
|
1700
1974
|
after: input.after,
|
|
1701
1975
|
eventType: input.eventTypes,
|
|
1702
1976
|
limit: input.limit,
|
|
1703
|
-
pollIntervalMs: input.pollIntervalMs,
|
|
1704
1977
|
},
|
|
1705
1978
|
});
|
|
1706
1979
|
}
|
|
@@ -1809,7 +2082,7 @@ export class StacklinkConnectedAccountsClient {
|
|
|
1809
2082
|
get(accountId) {
|
|
1810
2083
|
return this.client.getConnectedAccount(accountId);
|
|
1811
2084
|
}
|
|
1812
|
-
listTools(accountId, input
|
|
2085
|
+
listTools(accountId, input) {
|
|
1813
2086
|
return this.client.listConnectedAccountTools(accountId, input);
|
|
1814
2087
|
}
|
|
1815
2088
|
listResources(accountId) {
|
|
@@ -1872,14 +2145,14 @@ export class StacklinkToolkitsClient {
|
|
|
1872
2145
|
constructor(client) {
|
|
1873
2146
|
this.client = client;
|
|
1874
2147
|
}
|
|
1875
|
-
list() {
|
|
1876
|
-
return this.client.listProviders();
|
|
2148
|
+
list(query = {}) {
|
|
2149
|
+
return this.client.listProviders(query);
|
|
1877
2150
|
}
|
|
1878
2151
|
listProject(query = {}) {
|
|
1879
2152
|
return this.client.listProjectProviders(query);
|
|
1880
2153
|
}
|
|
1881
|
-
listTools(providerKey) {
|
|
1882
|
-
return this.client.listProviderTools(providerKey);
|
|
2154
|
+
listTools(providerKey, query = {}) {
|
|
2155
|
+
return this.client.listProviderTools(providerKey, query);
|
|
1883
2156
|
}
|
|
1884
2157
|
}
|
|
1885
2158
|
export class StacklinkProviderToolsClient {
|
|
@@ -1952,12 +2225,6 @@ export class StacklinkSessionMemoryClient {
|
|
|
1952
2225
|
...(reason ? { reason } : {}),
|
|
1953
2226
|
});
|
|
1954
2227
|
}
|
|
1955
|
-
restore(memoryId, reason) {
|
|
1956
|
-
return this.memoryClient.restore(memoryId, {
|
|
1957
|
-
sessionId: this.sessionId,
|
|
1958
|
-
...(reason ? { reason } : {}),
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
2228
|
}
|
|
1962
2229
|
export class StacklinkWorkbenchRun {
|
|
1963
2230
|
client;
|
|
@@ -1972,37 +2239,16 @@ export class StacklinkWorkbenchRun {
|
|
|
1972
2239
|
this.executionId = response.executionId;
|
|
1973
2240
|
this.executionKind = response.executionKind;
|
|
1974
2241
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
for (const event of page.events) {
|
|
1986
|
-
yield event;
|
|
1987
|
-
if (isSdkRecord(event) && (typeof event.sequence === 'string' || typeof event.sequence === 'number')) {
|
|
1988
|
-
afterSequence = String(event.sequence);
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
if (page.hasMore) {
|
|
1992
|
-
if (!page.nextCursor)
|
|
1993
|
-
throw new Error('Execution event page reported hasMore without nextCursor.');
|
|
1994
|
-
afterSequence = page.nextCursor;
|
|
1995
|
-
continue;
|
|
1996
|
-
}
|
|
1997
|
-
const state = await this.client.getExecution({
|
|
1998
|
-
sessionId: this.platformSessionId,
|
|
1999
|
-
executionKind: this.executionKind,
|
|
2000
|
-
executionId: this.executionId,
|
|
2001
|
-
});
|
|
2002
|
-
if (isTerminalSdkExecution(state.execution))
|
|
2003
|
-
return;
|
|
2004
|
-
await sleep(input.pollIntervalMs ?? 500);
|
|
2005
|
-
}
|
|
2242
|
+
events(input = {}) {
|
|
2243
|
+
return this.client.streamExecutionEvents({
|
|
2244
|
+
sessionId: this.platformSessionId,
|
|
2245
|
+
executionKind: this.executionKind,
|
|
2246
|
+
executionId: this.executionId,
|
|
2247
|
+
limit: input.limit ?? 200,
|
|
2248
|
+
}, {
|
|
2249
|
+
signal: input.signal,
|
|
2250
|
+
timeoutMs: input.streamTimeoutMs,
|
|
2251
|
+
});
|
|
2006
2252
|
}
|
|
2007
2253
|
wait(input = {}) {
|
|
2008
2254
|
return this.client.waitForExecution({
|
|
@@ -2261,8 +2507,8 @@ export class ToolRouterSession {
|
|
|
2261
2507
|
async mcpRequestStream(input, options = {}) {
|
|
2262
2508
|
return this.client.mcpSessionRequestStream(this.id, input, options);
|
|
2263
2509
|
}
|
|
2264
|
-
async callMcpTool(name, args) {
|
|
2265
|
-
return this.client.callSessionMcpTool(this.id, name, args);
|
|
2510
|
+
async callMcpTool(name, args, options = {}) {
|
|
2511
|
+
return this.client.callSessionMcpTool(this.id, name, args, options);
|
|
2266
2512
|
}
|
|
2267
2513
|
mcpConnection() {
|
|
2268
2514
|
if (!this.mcp) {
|
|
@@ -2285,6 +2531,7 @@ export class ToolRouterSession {
|
|
|
2285
2531
|
return this.toolDefinitions.map((definition) => ({
|
|
2286
2532
|
...definition,
|
|
2287
2533
|
inputSchema: { ...definition.inputSchema },
|
|
2534
|
+
outputSchema: { ...definition.outputSchema },
|
|
2288
2535
|
}));
|
|
2289
2536
|
}
|
|
2290
2537
|
async close() {
|
|
@@ -2305,18 +2552,17 @@ export class ToolRouterSession {
|
|
|
2305
2552
|
async workbenchActivity() {
|
|
2306
2553
|
return this.client.getSessionWorkbench(this.id);
|
|
2307
2554
|
}
|
|
2308
|
-
async toolkits() {
|
|
2309
|
-
|
|
2310
|
-
|
|
2555
|
+
async toolkits(query = {}) {
|
|
2556
|
+
return this.client.request(`/v1/sessions/${this.id}/toolkits`, {
|
|
2557
|
+
query: providerCatalogSdkQuery(query),
|
|
2558
|
+
});
|
|
2311
2559
|
}
|
|
2312
|
-
async authorize(
|
|
2560
|
+
async authorize(connectionRef, input = {}) {
|
|
2313
2561
|
const response = await this.client.request(`/v1/sessions/${this.id}/connections/authorize`, {
|
|
2314
2562
|
method: 'POST',
|
|
2315
2563
|
body: {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
authConfigId: input.authConfigId,
|
|
2319
|
-
authMode: input.authMode,
|
|
2564
|
+
connectionRef,
|
|
2565
|
+
oauthScopeProfile: input.oauthScopeProfile,
|
|
2320
2566
|
metadata: input.metadata ?? {},
|
|
2321
2567
|
},
|
|
2322
2568
|
});
|
|
@@ -2409,70 +2655,19 @@ export class ToolRouterSession {
|
|
|
2409
2655
|
this.client.clearCache(`session:${this.id}:`);
|
|
2410
2656
|
return response;
|
|
2411
2657
|
}
|
|
2412
|
-
async
|
|
2413
|
-
const searchInput = {
|
|
2414
|
-
|
|
2415
|
-
includeSchemas: false,
|
|
2416
|
-
includeRelatedTools: false,
|
|
2417
|
-
includeExecutionPlan: false,
|
|
2418
|
-
...input,
|
|
2419
|
-
};
|
|
2420
|
-
return this.client.cachedDiscoveryRequest(`session:${this.id}:search:${stableSdkStringify(withoutCacheControl(searchInput))}`, this.client.discoveryCacheTtl('search'), searchInput, () => this.client.request(`/v1/sessions/${this.id}/tools/search`, {
|
|
2658
|
+
async search(input) {
|
|
2659
|
+
const searchInput = { queries: input.queries };
|
|
2660
|
+
return this.client.cachedDiscoveryRequest(`session:${this.id}:search:${stableSdkStringify(withoutCacheControl(searchInput))}`, this.client.discoveryCacheTtl('search'), input, () => this.client.request(`/v1/sessions/${this.id}/tools/search`, {
|
|
2421
2661
|
method: 'POST',
|
|
2422
2662
|
body: discoveryServerBody(searchInput),
|
|
2423
2663
|
}));
|
|
2424
2664
|
}
|
|
2425
|
-
async
|
|
2426
|
-
return this.client.
|
|
2427
|
-
toolKeys: [...toolKeys].sort(),
|
|
2428
|
-
includePlanned: input.includePlanned === true,
|
|
2429
|
-
})}`, this.client.discoveryCacheTtl('schemas'), input, () => this.client.request(`/v1/sessions/${this.id}/tools/schemas`, {
|
|
2430
|
-
method: 'POST',
|
|
2431
|
-
body: {
|
|
2432
|
-
toolKeys,
|
|
2433
|
-
includePlanned: input.includePlanned,
|
|
2434
|
-
...(input.cache === 'bypass' ? { cache: 'bypass' } : {}),
|
|
2435
|
-
},
|
|
2436
|
-
}));
|
|
2437
|
-
}
|
|
2438
|
-
async prepareRuntimeTools(input = {}) {
|
|
2439
|
-
return this.client.request(`/v1/sessions/${this.id}/tools/runtime-contracts`, {
|
|
2665
|
+
async getContracts(contractRefs) {
|
|
2666
|
+
return this.client.request(`/v1/sessions/${this.id}/tools/contracts`, {
|
|
2440
2667
|
method: 'POST',
|
|
2441
|
-
body:
|
|
2668
|
+
body: { contractRefs },
|
|
2442
2669
|
});
|
|
2443
2670
|
}
|
|
2444
|
-
async prepareTools(input) {
|
|
2445
|
-
const limitPerIntent = input.limitPerIntent ?? 5;
|
|
2446
|
-
const search = await this.searchTools({
|
|
2447
|
-
cache: input.cache,
|
|
2448
|
-
responseMode: 'compact',
|
|
2449
|
-
includeSchemas: false,
|
|
2450
|
-
includeRelatedTools: false,
|
|
2451
|
-
includeExecutionPlan: false,
|
|
2452
|
-
queries: input.intents.map((intent) => ({
|
|
2453
|
-
query: intent,
|
|
2454
|
-
limit: limitPerIntent,
|
|
2455
|
-
...(input.toolkitKey ? { toolkitKey: input.toolkitKey } : {}),
|
|
2456
|
-
...(input.capability ? { capability: input.capability } : {}),
|
|
2457
|
-
})),
|
|
2458
|
-
includePlanned: input.includePlanned === true,
|
|
2459
|
-
model: input.model,
|
|
2460
|
-
limit: Math.max(limitPerIntent, input.intents.length * limitPerIntent),
|
|
2461
|
-
});
|
|
2462
|
-
const toolKeys = uniqueSdkStrings(search.tools.map((tool) => tool.key));
|
|
2463
|
-
const schemaResponse = input.includeSchemas === false || toolKeys.length === 0
|
|
2464
|
-
? { tools: [] }
|
|
2465
|
-
: await this.getToolSchemas(toolKeys, {
|
|
2466
|
-
cache: input.cache,
|
|
2467
|
-
includePlanned: input.includePlanned,
|
|
2468
|
-
});
|
|
2469
|
-
return {
|
|
2470
|
-
tools: search.tools,
|
|
2471
|
-
schemas: schemaResponse.tools,
|
|
2472
|
-
toolKeys,
|
|
2473
|
-
search,
|
|
2474
|
-
};
|
|
2475
|
-
}
|
|
2476
2671
|
async searchKnowledge(input = {}) {
|
|
2477
2672
|
return this.client.searchKnowledge({
|
|
2478
2673
|
...input,
|
|
@@ -2575,18 +2770,26 @@ export class ToolRouterSession {
|
|
|
2575
2770
|
sessionId: this.id,
|
|
2576
2771
|
});
|
|
2577
2772
|
}
|
|
2578
|
-
async
|
|
2773
|
+
async execute(contractRef, input, execution = {}, options = {}) {
|
|
2774
|
+
if (execution.idempotencyKey !== undefined && options.idempotencyKey !== undefined
|
|
2775
|
+
&& execution.idempotencyKey !== options.idempotencyKey) {
|
|
2776
|
+
throw new StacklinkApiError('Execution and request idempotency keys must match.', 400, 'idempotency_key_conflict');
|
|
2777
|
+
}
|
|
2778
|
+
const idempotencyKey = execution.idempotencyKey ?? options.idempotencyKey ?? createSdkRequestId();
|
|
2779
|
+
const { idempotencyKey: _childKey, ...item } = execution;
|
|
2579
2780
|
return this.client.request(`/v1/sessions/${this.id}/tools/execute`, {
|
|
2580
2781
|
method: 'POST',
|
|
2581
|
-
body:
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2782
|
+
body: {
|
|
2783
|
+
idempotencyKey,
|
|
2784
|
+
executions: [{
|
|
2785
|
+
contractRef,
|
|
2786
|
+
input,
|
|
2787
|
+
...item,
|
|
2788
|
+
}],
|
|
2789
|
+
},
|
|
2589
2790
|
...options,
|
|
2791
|
+
timeoutMs: options.timeoutMs ?? 60_000,
|
|
2792
|
+
idempotencyKey,
|
|
2590
2793
|
});
|
|
2591
2794
|
}
|
|
2592
2795
|
async runWorkbench(input, options = {}) {
|
|
@@ -2652,7 +2855,6 @@ export class ToolRouterSession {
|
|
|
2652
2855
|
after: input.after,
|
|
2653
2856
|
eventType: input.eventTypes,
|
|
2654
2857
|
limit: input.limit === undefined ? undefined : String(input.limit),
|
|
2655
|
-
pollIntervalMs: input.pollIntervalMs === undefined ? undefined : String(input.pollIntervalMs),
|
|
2656
2858
|
},
|
|
2657
2859
|
});
|
|
2658
2860
|
}
|
|
@@ -2662,7 +2864,8 @@ export class ToolRouterSession {
|
|
|
2662
2864
|
{
|
|
2663
2865
|
description: definition.description,
|
|
2664
2866
|
parameters: definition.inputSchema,
|
|
2665
|
-
|
|
2867
|
+
outputSchema: definition.outputSchema,
|
|
2868
|
+
execute: async (input, execution) => unwrapMcpToolResult(await this.callMcpTool(definition.name, input, { ...(execution?.abortSignal ? { signal: execution.abortSignal } : {}), ...(execution?.onProgress ? { onProgress: execution.onProgress } : {}) })),
|
|
2666
2869
|
},
|
|
2667
2870
|
]));
|
|
2668
2871
|
}
|
|
@@ -2779,4 +2982,10 @@ function isTerminalSdkExecution(value) {
|
|
|
2779
2982
|
|| status === 'cancelled'
|
|
2780
2983
|
|| status === 'timed_out';
|
|
2781
2984
|
}
|
|
2985
|
+
function validateSdkMcpResponse(value, requestId) {
|
|
2986
|
+
if (!isSdkRecord(value) || value.jsonrpc !== '2.0' || value.id !== requestId || ('result' in value) === ('error' in value)
|
|
2987
|
+
|| ('error' in value && (!isSdkRecord(value.error) || typeof value.error.code !== 'number' || typeof value.error.message !== 'string'))) {
|
|
2988
|
+
throw new StacklinkApiError('Stacklink returned an invalid or unrelated MCP response.', 502, 'mcp_response_invalid');
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2782
2991
|
//# sourceMappingURL=index.js.map
|