@kontext-dev/js-sdk 0.1.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.
Files changed (84) hide show
  1. package/README.md +70 -0
  2. package/dist/adapters/ai/index.cjs +175 -0
  3. package/dist/adapters/ai/index.cjs.map +1 -0
  4. package/dist/adapters/ai/index.d.cts +51 -0
  5. package/dist/adapters/ai/index.d.ts +51 -0
  6. package/dist/adapters/ai/index.js +173 -0
  7. package/dist/adapters/ai/index.js.map +1 -0
  8. package/dist/adapters/cloudflare/index.cjs +598 -0
  9. package/dist/adapters/cloudflare/index.cjs.map +1 -0
  10. package/dist/adapters/cloudflare/index.d.cts +214 -0
  11. package/dist/adapters/cloudflare/index.d.ts +214 -0
  12. package/dist/adapters/cloudflare/index.js +594 -0
  13. package/dist/adapters/cloudflare/index.js.map +1 -0
  14. package/dist/adapters/cloudflare/react.cjs +156 -0
  15. package/dist/adapters/cloudflare/react.cjs.map +1 -0
  16. package/dist/adapters/cloudflare/react.d.cts +68 -0
  17. package/dist/adapters/cloudflare/react.d.ts +68 -0
  18. package/dist/adapters/cloudflare/react.js +152 -0
  19. package/dist/adapters/cloudflare/react.js.map +1 -0
  20. package/dist/adapters/react/index.cjs +146 -0
  21. package/dist/adapters/react/index.cjs.map +1 -0
  22. package/dist/adapters/react/index.d.cts +103 -0
  23. package/dist/adapters/react/index.d.ts +103 -0
  24. package/dist/adapters/react/index.js +142 -0
  25. package/dist/adapters/react/index.js.map +1 -0
  26. package/dist/client/index.cjs +2415 -0
  27. package/dist/client/index.cjs.map +1 -0
  28. package/dist/client/index.d.cts +125 -0
  29. package/dist/client/index.d.ts +125 -0
  30. package/dist/client/index.js +2412 -0
  31. package/dist/client/index.js.map +1 -0
  32. package/dist/errors.cjs +213 -0
  33. package/dist/errors.cjs.map +1 -0
  34. package/dist/errors.d.cts +161 -0
  35. package/dist/errors.d.ts +161 -0
  36. package/dist/errors.js +201 -0
  37. package/dist/errors.js.map +1 -0
  38. package/dist/index-D5hS5PGn.d.ts +54 -0
  39. package/dist/index-DcL4a5Vq.d.cts +54 -0
  40. package/dist/index.cjs +4046 -0
  41. package/dist/index.cjs.map +1 -0
  42. package/dist/index.d.cts +15 -0
  43. package/dist/index.d.ts +15 -0
  44. package/dist/index.js +4029 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/kontext-CgIBANFo.d.cts +308 -0
  47. package/dist/kontext-CgIBANFo.d.ts +308 -0
  48. package/dist/management/index.cjs +867 -0
  49. package/dist/management/index.cjs.map +1 -0
  50. package/dist/management/index.d.cts +467 -0
  51. package/dist/management/index.d.ts +467 -0
  52. package/dist/management/index.js +855 -0
  53. package/dist/management/index.js.map +1 -0
  54. package/dist/mcp/index.cjs +799 -0
  55. package/dist/mcp/index.cjs.map +1 -0
  56. package/dist/mcp/index.d.cts +231 -0
  57. package/dist/mcp/index.d.ts +231 -0
  58. package/dist/mcp/index.js +797 -0
  59. package/dist/mcp/index.js.map +1 -0
  60. package/dist/oauth/index.cjs +418 -0
  61. package/dist/oauth/index.cjs.map +1 -0
  62. package/dist/oauth/index.d.cts +235 -0
  63. package/dist/oauth/index.d.ts +235 -0
  64. package/dist/oauth/index.js +414 -0
  65. package/dist/oauth/index.js.map +1 -0
  66. package/dist/server/index.cjs +1634 -0
  67. package/dist/server/index.cjs.map +1 -0
  68. package/dist/server/index.d.cts +10 -0
  69. package/dist/server/index.d.ts +10 -0
  70. package/dist/server/index.js +1629 -0
  71. package/dist/server/index.js.map +1 -0
  72. package/dist/types-CzhnlJHW.d.cts +397 -0
  73. package/dist/types-CzhnlJHW.d.ts +397 -0
  74. package/dist/types-RIzHnRpk.d.cts +23 -0
  75. package/dist/types-RIzHnRpk.d.ts +23 -0
  76. package/dist/verifier-CoJmYiw3.d.cts +109 -0
  77. package/dist/verifier-CoJmYiw3.d.ts +109 -0
  78. package/dist/verify/index.cjs +319 -0
  79. package/dist/verify/index.cjs.map +1 -0
  80. package/dist/verify/index.d.cts +63 -0
  81. package/dist/verify/index.d.ts +63 -0
  82. package/dist/verify/index.js +315 -0
  83. package/dist/verify/index.js.map +1 -0
  84. package/package.json +221 -0
@@ -0,0 +1,594 @@
1
+ // src/errors.ts
2
+ var KontextError = class extends Error {
3
+ /** Brand field for type narrowing without instanceof */
4
+ kontextError = true;
5
+ /** Machine-readable error code, always prefixed with `kontext_` */
6
+ code;
7
+ /** HTTP status code when applicable */
8
+ statusCode;
9
+ /** Auto-generated link to error documentation */
10
+ docsUrl;
11
+ /** Server request ID for debugging / support escalation */
12
+ requestId;
13
+ /** Contextual metadata bag (integration IDs, param names, etc.) */
14
+ meta;
15
+ constructor(message, code, options) {
16
+ super(message, { cause: options?.cause });
17
+ this.name = "KontextError";
18
+ this.code = code;
19
+ this.statusCode = options?.statusCode;
20
+ this.requestId = options?.requestId;
21
+ this.meta = options?.meta ?? {};
22
+ this.docsUrl = `https://docs.kontext.dev/errors/${code}`;
23
+ Object.setPrototypeOf(this, new.target.prototype);
24
+ }
25
+ toJSON() {
26
+ return {
27
+ name: this.name,
28
+ code: this.code,
29
+ message: this.message,
30
+ statusCode: this.statusCode,
31
+ docsUrl: this.docsUrl,
32
+ requestId: this.requestId,
33
+ meta: Object.keys(this.meta).length > 0 ? this.meta : void 0
34
+ };
35
+ }
36
+ toString() {
37
+ const parts = [`[${this.code}] ${this.message}`];
38
+ if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);
39
+ if (this.requestId) parts.push(`Request ID: ${this.requestId}`);
40
+ return parts.join("\n");
41
+ }
42
+ };
43
+ var OAuthError = class extends KontextError {
44
+ errorCode;
45
+ errorDescription;
46
+ constructor(message, code, options) {
47
+ super(message, code, {
48
+ statusCode: options?.statusCode ?? 400,
49
+ ...options
50
+ });
51
+ this.name = "OAuthError";
52
+ this.errorCode = options?.errorCode;
53
+ this.errorDescription = options?.errorDescription;
54
+ }
55
+ };
56
+ var IntegrationConnectionRequiredError = class extends KontextError {
57
+ integrationId;
58
+ integrationName;
59
+ connectUrl;
60
+ constructor(integrationId, options) {
61
+ super(
62
+ options?.message ?? `Connection to integration "${integrationId}" is required. Visit the connect URL to authorize.`,
63
+ "kontext_integration_connection_required",
64
+ { statusCode: 403, ...options }
65
+ );
66
+ this.name = "IntegrationConnectionRequiredError";
67
+ this.integrationId = integrationId;
68
+ this.integrationName = options?.integrationName;
69
+ this.connectUrl = options?.connectUrl;
70
+ }
71
+ };
72
+ var ConfigError = class extends KontextError {
73
+ constructor(message, code, options) {
74
+ super(message, code, options);
75
+ this.name = "ConfigError";
76
+ }
77
+ };
78
+
79
+ // src/client/tool-utils.ts
80
+ function buildKontextSystemPrompt(toolNames, integrations) {
81
+ if (toolNames.length === 0) return "";
82
+ const searchTool = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"));
83
+ const executeTool = toolNames.find((n) => n.endsWith("_EXECUTE_TOOL"));
84
+ const requestCapabilityTool = toolNames.find(
85
+ (n) => n.endsWith("_REQUEST_CAPABILITY")
86
+ );
87
+ let prompt = "You are a helpful assistant with access to various tools and integrations through Kontext.\nYou can help users with tasks across their connected services (GitHub, Linear, Slack, etc.).\nWhen a user asks about their services, use the available MCP tools to help them.\nThe user has already authenticated \u2014 tools run as the user with their permissions, including access to private repositories and org resources.";
88
+ if (searchTool && executeTool) {
89
+ prompt += `
90
+
91
+ You have access to external integrations through Kontext MCP tools:
92
+ - Call \`${searchTool}\` first to discover what tools/integrations are available.
93
+ - Then call \`${executeTool}\` with the tool_id and arguments to run a specific tool.
94
+ - When the user asks about "my repos", "my issues", etc., use the appropriate tool to look up their data directly. Do not ask for their username \u2014 the tools already know who they are.
95
+ Always start by searching for tools when the user asks about their connected services.`;
96
+ }
97
+ if (integrations.length > 0) {
98
+ const connected = integrations.filter((i) => i.connected);
99
+ const disconnected = integrations.filter((i) => !i.connected);
100
+ if (disconnected.length > 0) {
101
+ prompt += "\n\n## Integration Status";
102
+ if (connected.length > 0) {
103
+ prompt += `
104
+
105
+ You already have access to: ${connected.map((i) => i.name).join(", ")}`;
106
+ }
107
+ prompt += `
108
+
109
+ The following services require user authorization: ${disconnected.map((i) => i.name).join(", ")}`;
110
+ if (requestCapabilityTool) {
111
+ const example = disconnected[0]?.name ?? "GitHub";
112
+ prompt += `
113
+
114
+ **IMPORTANT:** When the user requests an action that requires one of these services:
115
+ 1. Call the \`${requestCapabilityTool}\` tool with \`capability_name\` set to the service name (e.g. "${example}")
116
+ 2. The tool will return an authorization link
117
+ 3. Include the link in your response so the user can click it to connect
118
+ 4. After the user authorizes, the service will be available on their next message`;
119
+ } else {
120
+ prompt += "\n\nWhen the user asks about a disconnected integration, let them know it needs to be connected first. Do not attempt to use tools from disconnected integrations without informing the user.";
121
+ }
122
+ }
123
+ }
124
+ return prompt;
125
+ }
126
+ function enrichToolsWithAuthAwareness(toolNames, integrations, options) {
127
+ const hasDisconnected = integrations.some(
128
+ (i) => !i.connected && i.connectUrl
129
+ );
130
+ if (!hasDisconnected) {
131
+ return {
132
+ systemPrompt: buildKontextSystemPrompt(toolNames, integrations),
133
+ requestCapabilityTool: null
134
+ };
135
+ }
136
+ const prefix = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"))?.replace(/_SEARCH_TOOLS$/, "") ?? "kontext";
137
+ const toolName = `${prefix}_REQUEST_CAPABILITY`;
138
+ const tool = buildRequestCapabilityTool(integrations);
139
+ const allToolNames = [...toolNames, toolName];
140
+ return {
141
+ systemPrompt: buildKontextSystemPrompt(allToolNames, integrations),
142
+ requestCapabilityTool: { name: toolName, ...tool }
143
+ };
144
+ }
145
+ function parseIntegrationStatus(tools, errors, elicitations) {
146
+ const seen = /* @__PURE__ */ new Set();
147
+ const result = [];
148
+ for (const t of tools) {
149
+ const sid = t.server?.id;
150
+ if (sid && !seen.has(sid)) {
151
+ seen.add(sid);
152
+ result.push({
153
+ id: sid,
154
+ name: t.server?.name ?? sid,
155
+ connected: true
156
+ });
157
+ }
158
+ }
159
+ for (const e of errors) {
160
+ if (!seen.has(e.serverId)) {
161
+ seen.add(e.serverId);
162
+ const elicitation = elicitations?.find(
163
+ (el) => el.integrationId === e.serverId
164
+ );
165
+ result.push({
166
+ id: e.serverId,
167
+ name: e.serverName ?? e.serverId,
168
+ connected: false,
169
+ connectUrl: elicitation?.url
170
+ });
171
+ }
172
+ }
173
+ return result;
174
+ }
175
+ function buildRequestCapabilityTool(capabilities) {
176
+ const disconnected = capabilities.filter((c) => !c.connected && c.connectUrl);
177
+ const capabilityList = disconnected.length > 0 ? disconnected.map((c) => c.name).join(", ") : "none";
178
+ return {
179
+ description: `Request connection to a capability that is not yet connected. Currently disconnected capabilities: ${capabilityList}. Call this tool with the capability name to initiate the connection flow.`,
180
+ parameters: {
181
+ type: "object",
182
+ properties: {
183
+ capability_name: {
184
+ type: "string",
185
+ description: "The name of the capability to connect"
186
+ }
187
+ },
188
+ required: ["capability_name"]
189
+ },
190
+ execute: async (...args) => {
191
+ const input = args[0] ?? {};
192
+ const name = input.capability_name ?? "";
193
+ if (!name && disconnected.length === 1 && disconnected[0]) {
194
+ return `${disconnected[0].name} requires authorization. Please visit the following link to connect: ${disconnected[0].connectUrl}`;
195
+ }
196
+ const match = disconnected.find(
197
+ (c) => c.name.toLowerCase() === name.toLowerCase()
198
+ );
199
+ if (!match) {
200
+ const isConnected = capabilities.find(
201
+ (c) => c.connected && c.name.toLowerCase() === name.toLowerCase()
202
+ );
203
+ if (isConnected) {
204
+ return `${isConnected.name} is already connected and ready to use.`;
205
+ }
206
+ return `Unknown capability "${name}". Available disconnected capabilities: ${capabilityList}.`;
207
+ }
208
+ return `${match.name} requires authorization. Please visit the following link to connect: ${match.connectUrl}`;
209
+ }
210
+ };
211
+ }
212
+ async function preflightIntegrationStatus(tools) {
213
+ const searchToolKey = Object.keys(tools).find(
214
+ (k) => k.endsWith("_SEARCH_TOOLS")
215
+ );
216
+ if (!searchToolKey || !tools[searchToolKey]?.execute) return [];
217
+ try {
218
+ const result = await tools[searchToolKey].execute({ limit: 100 });
219
+ let json;
220
+ if (typeof result === "string") {
221
+ json = result;
222
+ } else if (result && typeof result === "object") {
223
+ const content = result.content;
224
+ const first = content?.[0];
225
+ json = first?.resource?.text ?? first?.text;
226
+ }
227
+ if (typeof json === "string") {
228
+ const parsed = JSON.parse(json);
229
+ if (parsed && typeof parsed === "object") {
230
+ return parseIntegrationStatus(
231
+ Array.isArray(parsed.items) ? parsed.items : [],
232
+ Array.isArray(parsed.errors) ? parsed.errors : [],
233
+ Array.isArray(parsed.elicitations) ? parsed.elicitations : []
234
+ );
235
+ }
236
+ }
237
+ } catch (err) {
238
+ const elicitations = extractElicitations(err);
239
+ if (elicitations?.length) {
240
+ return elicitations.filter(
241
+ (e) => !!e.integrationId
242
+ ).map((e) => ({
243
+ id: e.integrationId,
244
+ name: e.integrationName ?? e.integrationId,
245
+ connected: false,
246
+ connectUrl: e.url
247
+ }));
248
+ }
249
+ }
250
+ return [];
251
+ }
252
+ function extractElicitations(err) {
253
+ const error = err;
254
+ if (error?.code === -32042 && error?.data?.elicitations?.length) {
255
+ return error.data.elicitations;
256
+ }
257
+ if (error?.cause?.code === -32042 && error?.cause?.data?.elicitations?.length) {
258
+ return error.cause.data.elicitations;
259
+ }
260
+ return void 0;
261
+ }
262
+ function wrapToolsWithElicitation(tools) {
263
+ return Object.fromEntries(
264
+ Object.entries(tools).map(([name, tool]) => [
265
+ name,
266
+ {
267
+ ...tool,
268
+ execute: tool.execute ? async (...args) => {
269
+ try {
270
+ return await tool.execute(...args);
271
+ } catch (err) {
272
+ const elicitations = extractElicitations(err);
273
+ const elicitation = elicitations?.[0];
274
+ if (elicitation?.url) {
275
+ const integrationId = elicitation.integrationId ?? "unknown";
276
+ throw new IntegrationConnectionRequiredError(integrationId, {
277
+ integrationName: elicitation.integrationName,
278
+ connectUrl: elicitation.url,
279
+ message: elicitation.message
280
+ });
281
+ }
282
+ throw err;
283
+ }
284
+ } : void 0
285
+ }
286
+ ])
287
+ );
288
+ }
289
+
290
+ // src/adapters/cloudflare/index.ts
291
+ var STATE_EXPIRATION_MS = 10 * 60 * 1e3;
292
+ var KontextCloudflareOAuthProvider = class {
293
+ authUrl;
294
+ serverId;
295
+ _clientId;
296
+ storage;
297
+ agentName;
298
+ _callbackUrl;
299
+ constructor(config) {
300
+ this._clientId = config.kontextClientId;
301
+ this.storage = config.storage;
302
+ this.agentName = config.agentName;
303
+ this._callbackUrl = config.callbackUrl;
304
+ }
305
+ get clientId() {
306
+ return this._clientId;
307
+ }
308
+ set clientId(_id) {
309
+ }
310
+ get redirectUrl() {
311
+ return this._callbackUrl;
312
+ }
313
+ get clientUri() {
314
+ return new URL(this._callbackUrl).origin;
315
+ }
316
+ get clientMetadata() {
317
+ return {
318
+ redirect_uris: [this._callbackUrl],
319
+ grant_types: ["authorization_code", "refresh_token"],
320
+ response_types: ["code"],
321
+ token_endpoint_auth_method: "none"
322
+ };
323
+ }
324
+ async clientInformation() {
325
+ return { client_id: this._clientId };
326
+ }
327
+ async saveClientInformation(info) {
328
+ if (info.client_id !== this._clientId) return;
329
+ }
330
+ tokenKey() {
331
+ return `kontext/${this.agentName}/${this._clientId}/tokens`;
332
+ }
333
+ async tokens() {
334
+ return await this.storage.get(this.tokenKey()) ?? void 0;
335
+ }
336
+ async saveTokens(tokens) {
337
+ await this.storage.put(this.tokenKey(), tokens);
338
+ }
339
+ stateKey(nonce) {
340
+ return `kontext/${this.agentName}/state/${nonce}`;
341
+ }
342
+ async state() {
343
+ const nonce = crypto.randomUUID();
344
+ const sid = this.serverId ?? "unknown";
345
+ const stored = {
346
+ nonce,
347
+ serverId: sid,
348
+ createdAt: Date.now()
349
+ };
350
+ await this.storage.put(this.stateKey(nonce), stored);
351
+ return `${nonce}.${sid}`;
352
+ }
353
+ async checkState(state) {
354
+ const dotIndex = state.indexOf(".");
355
+ if (dotIndex === -1) {
356
+ return { valid: false, error: "Invalid state format" };
357
+ }
358
+ const nonce = state.slice(0, dotIndex);
359
+ const serverId = state.slice(dotIndex + 1);
360
+ const stored = await this.storage.get(this.stateKey(nonce));
361
+ if (!stored) {
362
+ return { valid: false, error: "State not found or already used" };
363
+ }
364
+ if (stored.serverId !== serverId) {
365
+ await this.storage.delete(this.stateKey(nonce));
366
+ return { valid: false, error: "State serverId mismatch" };
367
+ }
368
+ const age = Date.now() - stored.createdAt;
369
+ if (age > STATE_EXPIRATION_MS) {
370
+ await this.storage.delete(this.stateKey(nonce));
371
+ return { valid: false, error: "State expired" };
372
+ }
373
+ return { valid: true, serverId };
374
+ }
375
+ async consumeState(state) {
376
+ const dotIndex = state.indexOf(".");
377
+ if (dotIndex === -1) return;
378
+ const nonce = state.slice(0, dotIndex);
379
+ await this.storage.delete(this.stateKey(nonce));
380
+ }
381
+ codeVerifierKey() {
382
+ return `kontext/${this.agentName}/${this._clientId}/code_verifier`;
383
+ }
384
+ async saveCodeVerifier(verifier) {
385
+ await this.storage.put(this.codeVerifierKey(), verifier);
386
+ }
387
+ async codeVerifier() {
388
+ const verifier = await this.storage.get(this.codeVerifierKey());
389
+ if (!verifier) {
390
+ throw new OAuthError(
391
+ "No PKCE code verifier found. The OAuth flow may have expired or storage was cleared. Restart the auth flow.",
392
+ "kontext_oauth_code_verifier_missing"
393
+ );
394
+ }
395
+ return verifier;
396
+ }
397
+ async deleteCodeVerifier() {
398
+ await this.storage.delete(this.codeVerifierKey());
399
+ }
400
+ async redirectToAuthorization(url) {
401
+ this.authUrl = url.toString();
402
+ }
403
+ async invalidateCredentials(scope) {
404
+ if (scope === "all" || scope === "tokens") {
405
+ await this.storage.delete(this.tokenKey());
406
+ }
407
+ if (scope === "all" || scope === "verifier") {
408
+ await this.storage.delete(this.codeVerifierKey());
409
+ }
410
+ }
411
+ };
412
+ var DEFAULT_KONTEXT_SERVER = "https://api.kontext.dev";
413
+ var CALLBACK_HOST_KEY = "kontext:callbackHost";
414
+ function toOrigin(value) {
415
+ if (!value) return void 0;
416
+ try {
417
+ const url = new URL(value);
418
+ return `${url.protocol}//${url.host}`;
419
+ } catch {
420
+ return void 0;
421
+ }
422
+ }
423
+ function isLocalBrowserOrigin(origin) {
424
+ try {
425
+ const url = new URL(origin);
426
+ return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
427
+ } catch {
428
+ return false;
429
+ }
430
+ }
431
+ function resolveLocalCallbackHost(request) {
432
+ const originHeader = toOrigin(request.headers.get("origin"));
433
+ if (originHeader && isLocalBrowserOrigin(originHeader)) return originHeader;
434
+ const refererHeader = toOrigin(request.headers.get("referer"));
435
+ if (refererHeader && isLocalBrowserOrigin(refererHeader))
436
+ return refererHeader;
437
+ const hostHeader = request.headers.get("host");
438
+ if (hostHeader) {
439
+ const candidate = `http://${hostHeader}`;
440
+ if (isLocalBrowserOrigin(candidate)) return toOrigin(candidate);
441
+ }
442
+ return void 0;
443
+ }
444
+ function findKontextConnection(agent) {
445
+ for (const [id, connection] of Object.entries(agent.mcp.mcpConnections)) {
446
+ if (!connection || typeof connection !== "object") continue;
447
+ const c = connection;
448
+ const state = c.connectionState ?? c.state;
449
+ if (state === "ready" || state === 3) return "ready";
450
+ return id;
451
+ }
452
+ if (agent.mcp.listServers) {
453
+ const servers = agent.mcp.listServers();
454
+ for (const server of servers) {
455
+ if (server.name === "kontext") return server.id;
456
+ }
457
+ }
458
+ return void 0;
459
+ }
460
+ function withKontext(Base) {
461
+ return class KontextAgent extends Base {
462
+ /** @internal */
463
+ _kontextCallbackHost;
464
+ /** @internal */
465
+ _kontextSystemPrompt = "";
466
+ /** @internal */
467
+ _kontextIntegrations = [];
468
+ // -- Lazy helpers --
469
+ /** @internal */
470
+ _kontextConfig() {
471
+ const clientId = this.env.KONTEXT_CLIENT_ID;
472
+ if (!clientId) {
473
+ throw new ConfigError(
474
+ "KONTEXT_CLIENT_ID environment variable is required. Set it in your Cloudflare Worker environment.",
475
+ "kontext_config_missing_client_id"
476
+ );
477
+ }
478
+ const serverUrl = (this.env.KONTEXT_SERVER_URL ?? DEFAULT_KONTEXT_SERVER).replace(/\/$/, "");
479
+ return {
480
+ clientId,
481
+ storage: this.ctx.storage,
482
+ agentName: this.name,
483
+ serverUrl
484
+ };
485
+ }
486
+ // -- Lifecycle hooks (auto-wired) --
487
+ createMcpOAuthProvider(callbackUrl) {
488
+ const cfg = this._kontextConfig();
489
+ return new KontextCloudflareOAuthProvider({
490
+ kontextClientId: cfg.clientId,
491
+ storage: cfg.storage,
492
+ agentName: cfg.agentName,
493
+ callbackUrl
494
+ });
495
+ }
496
+ onConnect(connection, ctx) {
497
+ if (super.onConnect) {
498
+ super.onConnect(connection, ctx);
499
+ }
500
+ const url = new URL(ctx.request.url);
501
+ const requestHost = `${url.protocol}//${url.host}`;
502
+ this._kontextCallbackHost = resolveLocalCallbackHost(ctx.request) ?? requestHost;
503
+ void this._kontextConfig().storage.put(
504
+ CALLBACK_HOST_KEY,
505
+ this._kontextCallbackHost
506
+ );
507
+ }
508
+ async onStart() {
509
+ if (super.onStart) {
510
+ await super.onStart();
511
+ }
512
+ this.mcp.configureOAuthCallback({
513
+ customHandler: () => new Response(
514
+ "<html><body><p>Authenticated! You can close this window.</p><script>window.close();</script></body></html>",
515
+ { headers: { "content-type": "text/html" }, status: 200 }
516
+ )
517
+ });
518
+ }
519
+ async onRequest(request) {
520
+ if (typeof this.mcp.onRequest === "function") {
521
+ const response = await this.mcp.onRequest(request);
522
+ if (response) return response;
523
+ }
524
+ if (super.onRequest) {
525
+ return super.onRequest(request);
526
+ }
527
+ return new Response("Not found", { status: 404 });
528
+ }
529
+ // -- Public API --
530
+ async kontextTools() {
531
+ const cfg = this._kontextConfig();
532
+ await this.mcp.ensureJsonSchema();
533
+ const kontextConnectionId = findKontextConnection(
534
+ this
535
+ );
536
+ if (kontextConnectionId !== "ready") {
537
+ if (kontextConnectionId) {
538
+ try {
539
+ if (this.removeMcpServer) {
540
+ await this.removeMcpServer(kontextConnectionId);
541
+ } else if (this.mcp.removeServer) {
542
+ await this.mcp.removeServer(kontextConnectionId);
543
+ }
544
+ } catch {
545
+ }
546
+ }
547
+ const host = this._kontextCallbackHost ?? await cfg.storage.get(CALLBACK_HOST_KEY);
548
+ if (!host) {
549
+ throw new ConfigError(
550
+ "No callbackHost available. Ensure onConnect() fires before kontextTools(). This usually means the WebSocket connection hasn't been established yet.",
551
+ "kontext_config_missing_callback_host"
552
+ );
553
+ }
554
+ await this.addMcpServer("kontext", `${cfg.serverUrl}/mcp`, {
555
+ callbackHost: host
556
+ });
557
+ }
558
+ const mcpTools = this.mcp.getAITools();
559
+ this._kontextIntegrations = await preflightIntegrationStatus(mcpTools);
560
+ const wrapped = wrapToolsWithElicitation(mcpTools);
561
+ const { systemPrompt, requestCapabilityTool } = enrichToolsWithAuthAwareness(
562
+ Object.keys(wrapped),
563
+ this._kontextIntegrations
564
+ );
565
+ if (requestCapabilityTool) {
566
+ wrapped[requestCapabilityTool.name] = requestCapabilityTool;
567
+ }
568
+ this._kontextSystemPrompt = systemPrompt;
569
+ return wrapped;
570
+ }
571
+ get kontextSystemPrompt() {
572
+ return this._kontextSystemPrompt;
573
+ }
574
+ };
575
+ }
576
+ var DurableObjectKontextStorage = class {
577
+ constructor(storage) {
578
+ this.storage = storage;
579
+ }
580
+ async getJson(key) {
581
+ return await this.storage.get(key) ?? void 0;
582
+ }
583
+ async setJson(key, value) {
584
+ if (value === void 0) {
585
+ await this.storage.delete(key);
586
+ } else {
587
+ await this.storage.put(key, value);
588
+ }
589
+ }
590
+ };
591
+
592
+ export { DurableObjectKontextStorage, KontextCloudflareOAuthProvider, withKontext };
593
+ //# sourceMappingURL=index.js.map
594
+ //# sourceMappingURL=index.js.map