@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
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @kontext-dev/js-sdk
2
+
3
+ Broker credentials between your AI agents and integrations.
4
+
5
+ [Website](https://kontex.dev)
6
+ [Documentation](https://docs.kontext.dev)
7
+ [Dashboard](https://app.kontext.dev)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @kontext-dev/js-sdk
13
+ ```
14
+
15
+ ## What You Get
16
+
17
+ - OAuth-based authentication and token handling for AI applications
18
+ - MCP tool discovery and execution through a typed client
19
+ - Server-side credential brokering per user and integration
20
+ - Management API client for applications, integrations, and service accounts
21
+ - React, Cloudflare, and AI adapter entry points
22
+
23
+ ## Quick Start (Client)
24
+
25
+ ```ts
26
+ import { createKontextClient } from "@kontext-dev/js-sdk/client";
27
+
28
+ const client = createKontextClient({
29
+ clientId: "app_your-client-id",
30
+ redirectUri: "http://localhost:3000/callback",
31
+ onAuthRequired: (url) => {
32
+ window.location.href = url.toString();
33
+ },
34
+ });
35
+
36
+ await client.connect();
37
+ const tools = await client.tools.list();
38
+ ```
39
+
40
+ ## Quick Start (Server)
41
+
42
+ ```ts
43
+ import { Kontext } from "@kontext-dev/js-sdk/server";
44
+
45
+ const kontext = new Kontext({
46
+ clientId: process.env.KONTEXT_CLIENT_ID!,
47
+ });
48
+
49
+ const githubCredential = await kontext.require("github", userAccessToken);
50
+ ```
51
+
52
+ ## Entry Points
53
+
54
+ - `@kontext-dev/js-sdk` (root convenience exports)
55
+ - `@kontext-dev/js-sdk/client`
56
+ - `@kontext-dev/js-sdk/server`
57
+ - `@kontext-dev/js-sdk/management`
58
+ - `@kontext-dev/js-sdk/react`
59
+ - `@kontext-dev/js-sdk/cloudflare`
60
+ - `@kontext-dev/js-sdk/ai`
61
+ - `@kontext-dev/js-sdk/mcp`
62
+ - `@kontext-dev/js-sdk/errors`
63
+ - `@kontext-dev/js-sdk/verify`
64
+ - `@kontext-dev/js-sdk/oauth`
65
+
66
+ ## Learn More
67
+
68
+ - TypeScript SDK overview: https://docs.kontext.dev/sdks/typescript
69
+ - Getting started guide: https://docs.kontext.dev/getting-started/quickstart
70
+ - Framework guides: https://docs.kontext.dev/frameworks/vercel-ai
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ var ai = require('ai');
4
+
5
+ // src/adapters/ai/index.ts
6
+
7
+ // src/client/tool-utils.ts
8
+ function buildKontextSystemPrompt(toolNames, integrations) {
9
+ if (toolNames.length === 0) return "";
10
+ const searchTool = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"));
11
+ const executeTool = toolNames.find((n) => n.endsWith("_EXECUTE_TOOL"));
12
+ const requestCapabilityTool = toolNames.find(
13
+ (n) => n.endsWith("_REQUEST_CAPABILITY")
14
+ );
15
+ 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.";
16
+ if (searchTool && executeTool) {
17
+ prompt += `
18
+
19
+ You have access to external integrations through Kontext MCP tools:
20
+ - Call \`${searchTool}\` first to discover what tools/integrations are available.
21
+ - Then call \`${executeTool}\` with the tool_id and arguments to run a specific tool.
22
+ - 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.
23
+ Always start by searching for tools when the user asks about their connected services.`;
24
+ }
25
+ if (integrations.length > 0) {
26
+ const connected = integrations.filter((i) => i.connected);
27
+ const disconnected = integrations.filter((i) => !i.connected);
28
+ if (disconnected.length > 0) {
29
+ prompt += "\n\n## Integration Status";
30
+ if (connected.length > 0) {
31
+ prompt += `
32
+
33
+ You already have access to: ${connected.map((i) => i.name).join(", ")}`;
34
+ }
35
+ prompt += `
36
+
37
+ The following services require user authorization: ${disconnected.map((i) => i.name).join(", ")}`;
38
+ if (requestCapabilityTool) {
39
+ const example = disconnected[0]?.name ?? "GitHub";
40
+ prompt += `
41
+
42
+ **IMPORTANT:** When the user requests an action that requires one of these services:
43
+ 1. Call the \`${requestCapabilityTool}\` tool with \`capability_name\` set to the service name (e.g. "${example}")
44
+ 2. The tool will return an authorization link
45
+ 3. Include the link in your response so the user can click it to connect
46
+ 4. After the user authorizes, the service will be available on their next message`;
47
+ } else {
48
+ 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.";
49
+ }
50
+ }
51
+ }
52
+ return prompt;
53
+ }
54
+ function enrichToolsWithAuthAwareness(toolNames, integrations, options) {
55
+ const hasDisconnected = integrations.some(
56
+ (i) => !i.connected && i.connectUrl
57
+ );
58
+ if (!hasDisconnected) {
59
+ return {
60
+ systemPrompt: buildKontextSystemPrompt(toolNames, integrations),
61
+ requestCapabilityTool: null
62
+ };
63
+ }
64
+ const prefix = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"))?.replace(/_SEARCH_TOOLS$/, "") ?? "kontext";
65
+ const toolName = `${prefix}_REQUEST_CAPABILITY`;
66
+ const tool = buildRequestCapabilityTool(integrations);
67
+ const allToolNames = [...toolNames, toolName];
68
+ return {
69
+ systemPrompt: buildKontextSystemPrompt(allToolNames, integrations),
70
+ requestCapabilityTool: { name: toolName, ...tool }
71
+ };
72
+ }
73
+ function buildRequestCapabilityTool(capabilities) {
74
+ const disconnected = capabilities.filter((c) => !c.connected && c.connectUrl);
75
+ const capabilityList = disconnected.length > 0 ? disconnected.map((c) => c.name).join(", ") : "none";
76
+ return {
77
+ 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.`,
78
+ parameters: {
79
+ type: "object",
80
+ properties: {
81
+ capability_name: {
82
+ type: "string",
83
+ description: "The name of the capability to connect"
84
+ }
85
+ },
86
+ required: ["capability_name"]
87
+ },
88
+ execute: async (...args) => {
89
+ const input = args[0] ?? {};
90
+ const name = input.capability_name ?? "";
91
+ if (!name && disconnected.length === 1 && disconnected[0]) {
92
+ return `${disconnected[0].name} requires authorization. Please visit the following link to connect: ${disconnected[0].connectUrl}`;
93
+ }
94
+ const match = disconnected.find(
95
+ (c) => c.name.toLowerCase() === name.toLowerCase()
96
+ );
97
+ if (!match) {
98
+ const isConnected = capabilities.find(
99
+ (c) => c.connected && c.name.toLowerCase() === name.toLowerCase()
100
+ );
101
+ if (isConnected) {
102
+ return `${isConnected.name} is already connected and ready to use.`;
103
+ }
104
+ return `Unknown capability "${name}". Available disconnected capabilities: ${capabilityList}.`;
105
+ }
106
+ return `${match.name} requires authorization. Please visit the following link to connect: ${match.connectUrl}`;
107
+ }
108
+ };
109
+ }
110
+
111
+ // src/adapters/ai/index.ts
112
+ async function toKontextTools(client, options) {
113
+ const [tools, integrations] = await Promise.all([
114
+ client.tools.list(),
115
+ client.integrations.list()
116
+ ]);
117
+ const coreTools = {};
118
+ const usedNames = /* @__PURE__ */ new Set();
119
+ for (const tool of tools) {
120
+ const name = buildToolName(tool, usedNames);
121
+ coreTools[name] = {
122
+ description: formatDescription(tool),
123
+ parameters: tool.inputSchema ? ai.jsonSchema(tool.inputSchema) : ai.jsonSchema({ type: "object", properties: {} }),
124
+ execute: async (args) => {
125
+ const r = await client.tools.execute(tool.id, args);
126
+ return (options?.formatResult ?? defaultFormat)(r);
127
+ }
128
+ };
129
+ }
130
+ const { systemPrompt, requestCapabilityTool } = enrichToolsWithAuthAwareness(
131
+ tools.map((t) => t.name),
132
+ integrations
133
+ );
134
+ if (requestCapabilityTool) {
135
+ coreTools[requestCapabilityTool.name] = {
136
+ description: requestCapabilityTool.description,
137
+ parameters: ai.jsonSchema(
138
+ requestCapabilityTool.parameters
139
+ ),
140
+ execute: async (args) => {
141
+ return requestCapabilityTool.execute(args);
142
+ }
143
+ };
144
+ }
145
+ return { tools: coreTools, systemPrompt, integrations };
146
+ }
147
+ function defaultFormat(r) {
148
+ return r.content;
149
+ }
150
+ function formatDescription(tool) {
151
+ const base = tool.description ?? tool.name;
152
+ const serverLabel = tool.server?.name ?? tool.server?.id;
153
+ return serverLabel ? `[${serverLabel}] ${base}` : base;
154
+ }
155
+ function buildToolName(tool, usedNames) {
156
+ const rawName = tool.name || tool.id || "tool";
157
+ const base = tool.server ? `${tool.server.name ?? tool.server.id ?? "server"}_${rawName}` : rawName;
158
+ const sanitized = sanitizeToolName(base);
159
+ const fallback = sanitized || `tool_${usedNames.size + 1}`;
160
+ let name = fallback;
161
+ let suffix = 1;
162
+ while (usedNames.has(name)) {
163
+ name = `${fallback}_${suffix}`;
164
+ suffix += 1;
165
+ }
166
+ usedNames.add(name);
167
+ return name;
168
+ }
169
+ function sanitizeToolName(name) {
170
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
171
+ }
172
+
173
+ exports.toKontextTools = toKontextTools;
174
+ //# sourceMappingURL=index.cjs.map
175
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/client/tool-utils.ts","../../../src/adapters/ai/index.ts"],"names":["jsonSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Shared utilities for Kontext tool handling.\n * Used by both KontextClient (core) and withKontext (Cloudflare adapter).\n */\n\nimport {\n IntegrationConnectionRequiredError,\n type ElicitationEntry,\n} from \"../errors.js\";\n\n// Re-export for backward compatibility\nexport type { ElicitationEntry } from \"../errors.js\";\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport interface IntegrationStatus {\n readonly id: string;\n readonly name: string;\n readonly connected: boolean;\n readonly connectUrl?: string;\n}\n\n// ============================================================================\n// System prompt\n// ============================================================================\n\n/**\n * Build an LLM system prompt based on available tool names and integration status.\n * Detects `*_SEARCH_TOOLS` / `*_EXECUTE_TOOL` suffixed names from the\n * Cloudflare Agents MCP proxy and generates appropriate guidance.\n */\nexport function buildKontextSystemPrompt(\n toolNames: readonly string[],\n integrations: readonly IntegrationStatus[],\n): string {\n if (toolNames.length === 0) return \"\";\n\n const searchTool = toolNames.find((n) => n.endsWith(\"_SEARCH_TOOLS\"));\n const executeTool = toolNames.find((n) => n.endsWith(\"_EXECUTE_TOOL\"));\n const requestCapabilityTool = toolNames.find((n) =>\n n.endsWith(\"_REQUEST_CAPABILITY\"),\n );\n\n let prompt =\n \"You are a helpful assistant with access to various tools and integrations through Kontext.\\n\" +\n \"You can help users with tasks across their connected services (GitHub, Linear, Slack, etc.).\\n\" +\n \"When a user asks about their services, use the available MCP tools to help them.\\n\" +\n \"The user has already authenticated — tools run as the user with their permissions, including access to private repositories and org resources.\";\n\n if (searchTool && executeTool) {\n prompt +=\n `\\n\\nYou have access to external integrations through Kontext MCP tools:\\n` +\n `- Call \\`${searchTool}\\` first to discover what tools/integrations are available.\\n` +\n `- Then call \\`${executeTool}\\` with the tool_id and arguments to run a specific tool.\\n` +\n `- 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 — the tools already know who they are.\\n` +\n `Always start by searching for tools when the user asks about their connected services.`;\n }\n\n // Append integration status section when there are integrations to report\n if (integrations.length > 0) {\n const connected = integrations.filter((i) => i.connected);\n const disconnected = integrations.filter((i) => !i.connected);\n\n if (disconnected.length > 0) {\n prompt += \"\\n\\n## Integration Status\";\n\n if (connected.length > 0) {\n prompt += `\\n\\nYou already have access to: ${connected.map((i) => i.name).join(\", \")}`;\n }\n\n prompt += `\\n\\nThe following services require user authorization: ${disconnected.map((i) => i.name).join(\", \")}`;\n\n if (requestCapabilityTool) {\n const example = disconnected[0]?.name ?? \"GitHub\";\n prompt +=\n `\\n\\n**IMPORTANT:** When the user requests an action that requires one of these services:` +\n `\\n1. Call the \\`${requestCapabilityTool}\\` tool with \\`capability_name\\` set to the service name (e.g. \"${example}\")` +\n `\\n2. The tool will return an authorization link` +\n `\\n3. Include the link in your response so the user can click it to connect` +\n `\\n4. After the user authorizes, the service will be available on their next message`;\n } else {\n prompt +=\n \"\\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.\";\n }\n }\n }\n\n return prompt;\n}\n\n// ============================================================================\n// Auth-aware toolset orchestrator\n// ============================================================================\n\nexport interface AuthAwareToolset {\n /** System prompt with integration status and REQUEST_CAPABILITY instructions. */\n readonly systemPrompt: string;\n /** REQUEST_CAPABILITY tool to inject, or null if all integrations are connected. */\n readonly requestCapabilityTool: {\n readonly name: string;\n readonly description: string;\n readonly parameters: Record<string, unknown>;\n readonly execute: (...args: unknown[]) => Promise<string>;\n } | null;\n}\n\n/**\n * Enrich a tool set with auth awareness.\n *\n * Given tool names and integration status, produces:\n * - A system prompt that tells the LLM about connected/disconnected integrations\n * - A REQUEST_CAPABILITY tool (if needed) that returns auth URLs as text\n *\n * Both `ai/` and `cloudflare/` adapters use this as their core auth layer.\n */\nexport function enrichToolsWithAuthAwareness(\n toolNames: readonly string[],\n integrations: readonly IntegrationStatus[],\n options?: { prefix?: string },\n): AuthAwareToolset {\n const hasDisconnected = integrations.some(\n (i) => !i.connected && i.connectUrl,\n );\n\n if (!hasDisconnected) {\n return {\n systemPrompt: buildKontextSystemPrompt(toolNames, integrations),\n requestCapabilityTool: null,\n };\n }\n\n // Determine prefix from existing tool names or option\n const prefix =\n options?.prefix ??\n toolNames\n .find((n) => n.endsWith(\"_SEARCH_TOOLS\"))\n ?.replace(/_SEARCH_TOOLS$/, \"\") ??\n \"kontext\";\n\n const toolName = `${prefix}_REQUEST_CAPABILITY`;\n const tool = buildRequestCapabilityTool(integrations);\n\n // Include REQUEST_CAPABILITY in tool names so the system prompt references it\n const allToolNames = [...toolNames, toolName];\n\n return {\n systemPrompt: buildKontextSystemPrompt(allToolNames, integrations),\n requestCapabilityTool: { name: toolName, ...tool },\n };\n}\n\n// ============================================================================\n// Integration status parsing\n// ============================================================================\n\n/**\n * Parse integration status from gateway tool search results.\n * Extracts connected integrations from tools and disconnected from errors.\n */\nexport function parseIntegrationStatus(\n tools: ReadonlyArray<{ server?: { id?: string; name?: string } }>,\n errors: ReadonlyArray<{ serverId: string; serverName?: string }>,\n elicitations?: ReadonlyArray<{\n url: string;\n integrationId?: string;\n integrationName?: string;\n }>,\n): IntegrationStatus[] {\n const seen = new Set<string>();\n const result: IntegrationStatus[] = [];\n\n for (const t of tools) {\n const sid = t.server?.id;\n if (sid && !seen.has(sid)) {\n seen.add(sid);\n result.push({\n id: sid,\n name: t.server?.name ?? sid,\n connected: true,\n });\n }\n }\n\n for (const e of errors) {\n if (!seen.has(e.serverId)) {\n seen.add(e.serverId);\n const elicitation = elicitations?.find(\n (el) => el.integrationId === e.serverId,\n );\n result.push({\n id: e.serverId,\n name: e.serverName ?? e.serverId,\n connected: false,\n connectUrl: elicitation?.url,\n });\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Request capability tool\n// ============================================================================\n\n/**\n * Build a tool that lets the LLM request connection of a disconnected capability.\n * Returns the connect URL as text so the LLM can present it to the user.\n * No popups — the user clicks the link in the chat to authorize.\n */\nexport function buildRequestCapabilityTool(\n capabilities: readonly IntegrationStatus[],\n): {\n description: string;\n parameters: Record<string, unknown>;\n execute: (...args: unknown[]) => Promise<string>;\n} {\n const disconnected = capabilities.filter((c) => !c.connected && c.connectUrl);\n\n const capabilityList =\n disconnected.length > 0\n ? disconnected.map((c) => c.name).join(\", \")\n : \"none\";\n\n return {\n description:\n `Request connection to a capability that is not yet connected. ` +\n `Currently disconnected capabilities: ${capabilityList}. ` +\n `Call this tool with the capability name to initiate the connection flow.`,\n parameters: {\n type: \"object\",\n properties: {\n capability_name: {\n type: \"string\",\n description: \"The name of the capability to connect\",\n },\n },\n required: [\"capability_name\"],\n },\n execute: async (...args: unknown[]): Promise<string> => {\n const input = (args[0] ?? {}) as { capability_name?: string };\n const name = input.capability_name ?? \"\";\n\n // Auto-select when called without a name and there's only one option\n if (!name && disconnected.length === 1 && disconnected[0]) {\n return (\n `${disconnected[0].name} requires authorization. ` +\n `Please visit the following link to connect: ${disconnected[0].connectUrl}`\n );\n }\n\n const match = disconnected.find(\n (c) => c.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (!match) {\n const isConnected = capabilities.find(\n (c) => c.connected && c.name.toLowerCase() === name.toLowerCase(),\n );\n if (isConnected) {\n return `${isConnected.name} is already connected and ready to use.`;\n }\n return `Unknown capability \"${name}\". Available disconnected capabilities: ${capabilityList}.`;\n }\n\n return (\n `${match.name} requires authorization. ` +\n `Please visit the following link to connect: ${match.connectUrl}`\n );\n },\n };\n}\n\n/**\n * Pre-flight integration status detection.\n * Calls SEARCH_TOOLS on raw (unwrapped) tools to detect connected/disconnected\n * integrations without triggering any popups or broadcasts.\n */\nexport async function preflightIntegrationStatus(\n tools: ToolSetLike,\n): Promise<IntegrationStatus[]> {\n const searchToolKey = Object.keys(tools).find((k) =>\n k.endsWith(\"_SEARCH_TOOLS\"),\n );\n if (!searchToolKey || !tools[searchToolKey]?.execute) return [];\n\n try {\n const result = await tools[searchToolKey].execute!({ limit: 100 });\n\n // Cloudflare getAITools() returns MCP CallToolResult objects, not strings.\n // Extract JSON text from either a plain string or a CallToolResult shape.\n let json: string | undefined;\n if (typeof result === \"string\") {\n json = result;\n } else if (result && typeof result === \"object\") {\n const content = (\n result as {\n content?: Array<{\n type: string;\n resource?: { text?: string };\n text?: string;\n }>;\n }\n ).content;\n const first = content?.[0];\n json = first?.resource?.text ?? first?.text;\n }\n\n if (typeof json === \"string\") {\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === \"object\") {\n return parseIntegrationStatus(\n Array.isArray(parsed.items) ? parsed.items : [],\n Array.isArray(parsed.errors) ? parsed.errors : [],\n Array.isArray(parsed.elicitations) ? parsed.elicitations : [],\n );\n }\n }\n } catch (err: unknown) {\n // All integrations disconnected: -32042 error contains elicitations\n const elicitations = extractElicitations(err);\n if (elicitations?.length) {\n return elicitations\n .filter(\n (e): e is ElicitationEntry & { integrationId: string } =>\n !!e.integrationId,\n )\n .map((e) => ({\n id: e.integrationId,\n name: e.integrationName ?? e.integrationId,\n connected: false,\n connectUrl: e.url,\n }));\n }\n }\n\n return [];\n}\n\n// ============================================================================\n// Tool wrapping\n// ============================================================================\n\n/** A tool with an optional async execute method */\nexport interface ToolLike {\n execute?: (...args: unknown[]) => Promise<unknown>;\n [key: string]: unknown;\n}\n\n/** Record of named tools */\nexport type ToolSetLike = Record<string, ToolLike>;\n\n/**\n * Extract elicitation entries from an error thrown by the MCP SDK.\n * Handles two shapes: direct code/data and nested cause.\n */\nexport function extractElicitations(\n err: unknown,\n): ElicitationEntry[] | undefined {\n const error = err as {\n code?: number;\n data?: { elicitations?: ElicitationEntry[] };\n cause?: {\n code?: number;\n data?: { elicitations?: ElicitationEntry[] };\n };\n };\n\n // Primary: structured McpError with code -32042\n if (error?.code === -32042 && error?.data?.elicitations?.length) {\n return error.data.elicitations;\n }\n\n // Nested cause (some SDK wrappers nest the original error)\n if (\n error?.cause?.code === -32042 &&\n error?.cause?.data?.elicitations?.length\n ) {\n return error.cause.data.elicitations;\n }\n\n return undefined;\n}\n\n/**\n * Wrap tools to catch MCP error code -32042 (URL elicitation required)\n * and convert to IntegrationConnectionRequiredError.\n */\nexport function wrapToolsWithElicitation<T extends ToolSetLike>(tools: T): T {\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => [\n name,\n {\n ...tool,\n execute: tool.execute\n ? async (...args: unknown[]) => {\n try {\n return await tool.execute!(...args);\n } catch (err: unknown) {\n const elicitations = extractElicitations(err);\n const elicitation = elicitations?.[0];\n if (elicitation?.url) {\n const integrationId = elicitation.integrationId ?? \"unknown\";\n throw new IntegrationConnectionRequiredError(integrationId, {\n integrationName: elicitation.integrationName,\n connectUrl: elicitation.url,\n message: elicitation.message,\n });\n }\n throw err;\n }\n }\n : undefined,\n },\n ]),\n ) as T;\n}\n","/**\n * Vercel AI SDK adapter for Kontext.\n *\n * Pure format converter: maps KontextClient tools to AI SDK CoreTools.\n *\n * @example\n * ```typescript\n * import { createKontextClient } from '@kontext-dev/js-sdk';\n * import { toKontextTools } from '@kontext-dev/js-sdk/ai';\n * import { generateText } from 'ai';\n *\n * const client = createKontextClient({\n * clientId: process.env.KONTEXT_CLIENT_ID!,\n * redirectUri: 'http://localhost:3000/callback',\n * onAuthRequired: (url) => open(url.toString()),\n * });\n *\n * const { tools, systemPrompt } = await toKontextTools(client);\n * const result = await generateText({ model, system: systemPrompt, tools, prompt: '...', maxSteps: 5 });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { jsonSchema, type CoreTool } from \"ai\";\nimport type {\n KontextClient,\n KontextTool,\n IntegrationInfo,\n ToolResult,\n} from \"../../client/index.js\";\nimport type { KontextOrchestrator } from \"../../client/orchestrator/index.js\";\nimport { enrichToolsWithAuthAwareness } from \"../../client/tool-utils.js\";\n\nexport interface ToKontextToolsOptions {\n formatResult?: (result: ToolResult) => unknown;\n}\n\nexport interface KontextToolsResult {\n readonly tools: Record<string, CoreTool>;\n readonly systemPrompt: string;\n readonly integrations: readonly IntegrationInfo[];\n}\n\n/**\n * Convert a KontextClient's tools into an AI SDK ToolSet with system prompt.\n *\n * Calls `client.tools.list()` and `client.integrations.list()` to discover\n * tools and integration status, then wraps each tool as an AI SDK `CoreTool`\n * whose `execute` delegates to `client.tools.execute()`.\n */\nexport async function toKontextTools(\n client: KontextClient | KontextOrchestrator,\n options?: ToKontextToolsOptions,\n): Promise<KontextToolsResult> {\n const [tools, integrations] = await Promise.all([\n client.tools.list(),\n client.integrations.list(),\n ]);\n\n const coreTools: Record<string, CoreTool> = {};\n const usedNames = new Set<string>();\n\n for (const tool of tools) {\n const name = buildToolName(tool, usedNames);\n coreTools[name] = {\n description: formatDescription(tool),\n parameters: tool.inputSchema\n ? jsonSchema(tool.inputSchema as Parameters<typeof jsonSchema>[0])\n : jsonSchema({ type: \"object\" as const, properties: {} }),\n execute: async (args: Record<string, unknown>) => {\n const r = await client.tools.execute(tool.id, args);\n return (options?.formatResult ?? defaultFormat)(r);\n },\n };\n }\n\n const { systemPrompt, requestCapabilityTool } = enrichToolsWithAuthAwareness(\n tools.map((t) => t.name),\n integrations,\n );\n\n if (requestCapabilityTool) {\n coreTools[requestCapabilityTool.name] = {\n description: requestCapabilityTool.description,\n parameters: jsonSchema(\n requestCapabilityTool.parameters as Parameters<typeof jsonSchema>[0],\n ),\n execute: async (args: Record<string, unknown>) => {\n return requestCapabilityTool.execute(args);\n },\n };\n }\n\n return { tools: coreTools, systemPrompt, integrations };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultFormat(r: ToolResult): unknown {\n return r.content;\n}\n\nfunction formatDescription(tool: KontextTool): string {\n const base = tool.description ?? tool.name;\n const serverLabel = tool.server?.name ?? tool.server?.id;\n return serverLabel ? `[${serverLabel}] ${base}` : base;\n}\n\nfunction buildToolName(tool: KontextTool, usedNames: Set<string>): string {\n const rawName = tool.name || tool.id || \"tool\";\n const base = tool.server\n ? `${tool.server.name ?? tool.server.id ?? \"server\"}_${rawName}`\n : rawName;\n const sanitized = sanitizeToolName(base);\n const fallback = sanitized || `tool_${usedNames.size + 1}`;\n\n let name = fallback;\n let suffix = 1;\n while (usedNames.has(name)) {\n name = `${fallback}_${suffix}`;\n suffix += 1;\n }\n\n usedNames.add(name);\n return name;\n}\n\nfunction sanitizeToolName(name: string): string {\n return name\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 64);\n}\n"]}
@@ -0,0 +1,51 @@
1
+ import { CoreTool } from 'ai';
2
+ import { IntegrationInfo, ToolResult, KontextClient } from '../../client/index.cjs';
3
+ import { K as KontextOrchestrator } from '../../index-DcL4a5Vq.cjs';
4
+ import '../../mcp/index.cjs';
5
+ import '@modelcontextprotocol/sdk/client/index.js';
6
+ import '@modelcontextprotocol/sdk/types.js';
7
+ import '../../types-RIzHnRpk.cjs';
8
+ import '../../errors.cjs';
9
+
10
+ /**
11
+ * Vercel AI SDK adapter for Kontext.
12
+ *
13
+ * Pure format converter: maps KontextClient tools to AI SDK CoreTools.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createKontextClient } from '@kontext-dev/js-sdk';
18
+ * import { toKontextTools } from '@kontext-dev/js-sdk/ai';
19
+ * import { generateText } from 'ai';
20
+ *
21
+ * const client = createKontextClient({
22
+ * clientId: process.env.KONTEXT_CLIENT_ID!,
23
+ * redirectUri: 'http://localhost:3000/callback',
24
+ * onAuthRequired: (url) => open(url.toString()),
25
+ * });
26
+ *
27
+ * const { tools, systemPrompt } = await toKontextTools(client);
28
+ * const result = await generateText({ model, system: systemPrompt, tools, prompt: '...', maxSteps: 5 });
29
+ * ```
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+
34
+ interface ToKontextToolsOptions {
35
+ formatResult?: (result: ToolResult) => unknown;
36
+ }
37
+ interface KontextToolsResult {
38
+ readonly tools: Record<string, CoreTool>;
39
+ readonly systemPrompt: string;
40
+ readonly integrations: readonly IntegrationInfo[];
41
+ }
42
+ /**
43
+ * Convert a KontextClient's tools into an AI SDK ToolSet with system prompt.
44
+ *
45
+ * Calls `client.tools.list()` and `client.integrations.list()` to discover
46
+ * tools and integration status, then wraps each tool as an AI SDK `CoreTool`
47
+ * whose `execute` delegates to `client.tools.execute()`.
48
+ */
49
+ declare function toKontextTools(client: KontextClient | KontextOrchestrator, options?: ToKontextToolsOptions): Promise<KontextToolsResult>;
50
+
51
+ export { type KontextToolsResult, type ToKontextToolsOptions, toKontextTools };
@@ -0,0 +1,51 @@
1
+ import { CoreTool } from 'ai';
2
+ import { IntegrationInfo, ToolResult, KontextClient } from '../../client/index.js';
3
+ import { K as KontextOrchestrator } from '../../index-D5hS5PGn.js';
4
+ import '../../mcp/index.js';
5
+ import '@modelcontextprotocol/sdk/client/index.js';
6
+ import '@modelcontextprotocol/sdk/types.js';
7
+ import '../../types-RIzHnRpk.js';
8
+ import '../../errors.js';
9
+
10
+ /**
11
+ * Vercel AI SDK adapter for Kontext.
12
+ *
13
+ * Pure format converter: maps KontextClient tools to AI SDK CoreTools.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { createKontextClient } from '@kontext-dev/js-sdk';
18
+ * import { toKontextTools } from '@kontext-dev/js-sdk/ai';
19
+ * import { generateText } from 'ai';
20
+ *
21
+ * const client = createKontextClient({
22
+ * clientId: process.env.KONTEXT_CLIENT_ID!,
23
+ * redirectUri: 'http://localhost:3000/callback',
24
+ * onAuthRequired: (url) => open(url.toString()),
25
+ * });
26
+ *
27
+ * const { tools, systemPrompt } = await toKontextTools(client);
28
+ * const result = await generateText({ model, system: systemPrompt, tools, prompt: '...', maxSteps: 5 });
29
+ * ```
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+
34
+ interface ToKontextToolsOptions {
35
+ formatResult?: (result: ToolResult) => unknown;
36
+ }
37
+ interface KontextToolsResult {
38
+ readonly tools: Record<string, CoreTool>;
39
+ readonly systemPrompt: string;
40
+ readonly integrations: readonly IntegrationInfo[];
41
+ }
42
+ /**
43
+ * Convert a KontextClient's tools into an AI SDK ToolSet with system prompt.
44
+ *
45
+ * Calls `client.tools.list()` and `client.integrations.list()` to discover
46
+ * tools and integration status, then wraps each tool as an AI SDK `CoreTool`
47
+ * whose `execute` delegates to `client.tools.execute()`.
48
+ */
49
+ declare function toKontextTools(client: KontextClient | KontextOrchestrator, options?: ToKontextToolsOptions): Promise<KontextToolsResult>;
50
+
51
+ export { type KontextToolsResult, type ToKontextToolsOptions, toKontextTools };
@@ -0,0 +1,173 @@
1
+ import { jsonSchema } from 'ai';
2
+
3
+ // src/adapters/ai/index.ts
4
+
5
+ // src/client/tool-utils.ts
6
+ function buildKontextSystemPrompt(toolNames, integrations) {
7
+ if (toolNames.length === 0) return "";
8
+ const searchTool = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"));
9
+ const executeTool = toolNames.find((n) => n.endsWith("_EXECUTE_TOOL"));
10
+ const requestCapabilityTool = toolNames.find(
11
+ (n) => n.endsWith("_REQUEST_CAPABILITY")
12
+ );
13
+ 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.";
14
+ if (searchTool && executeTool) {
15
+ prompt += `
16
+
17
+ You have access to external integrations through Kontext MCP tools:
18
+ - Call \`${searchTool}\` first to discover what tools/integrations are available.
19
+ - Then call \`${executeTool}\` with the tool_id and arguments to run a specific tool.
20
+ - 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.
21
+ Always start by searching for tools when the user asks about their connected services.`;
22
+ }
23
+ if (integrations.length > 0) {
24
+ const connected = integrations.filter((i) => i.connected);
25
+ const disconnected = integrations.filter((i) => !i.connected);
26
+ if (disconnected.length > 0) {
27
+ prompt += "\n\n## Integration Status";
28
+ if (connected.length > 0) {
29
+ prompt += `
30
+
31
+ You already have access to: ${connected.map((i) => i.name).join(", ")}`;
32
+ }
33
+ prompt += `
34
+
35
+ The following services require user authorization: ${disconnected.map((i) => i.name).join(", ")}`;
36
+ if (requestCapabilityTool) {
37
+ const example = disconnected[0]?.name ?? "GitHub";
38
+ prompt += `
39
+
40
+ **IMPORTANT:** When the user requests an action that requires one of these services:
41
+ 1. Call the \`${requestCapabilityTool}\` tool with \`capability_name\` set to the service name (e.g. "${example}")
42
+ 2. The tool will return an authorization link
43
+ 3. Include the link in your response so the user can click it to connect
44
+ 4. After the user authorizes, the service will be available on their next message`;
45
+ } else {
46
+ 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.";
47
+ }
48
+ }
49
+ }
50
+ return prompt;
51
+ }
52
+ function enrichToolsWithAuthAwareness(toolNames, integrations, options) {
53
+ const hasDisconnected = integrations.some(
54
+ (i) => !i.connected && i.connectUrl
55
+ );
56
+ if (!hasDisconnected) {
57
+ return {
58
+ systemPrompt: buildKontextSystemPrompt(toolNames, integrations),
59
+ requestCapabilityTool: null
60
+ };
61
+ }
62
+ const prefix = toolNames.find((n) => n.endsWith("_SEARCH_TOOLS"))?.replace(/_SEARCH_TOOLS$/, "") ?? "kontext";
63
+ const toolName = `${prefix}_REQUEST_CAPABILITY`;
64
+ const tool = buildRequestCapabilityTool(integrations);
65
+ const allToolNames = [...toolNames, toolName];
66
+ return {
67
+ systemPrompt: buildKontextSystemPrompt(allToolNames, integrations),
68
+ requestCapabilityTool: { name: toolName, ...tool }
69
+ };
70
+ }
71
+ function buildRequestCapabilityTool(capabilities) {
72
+ const disconnected = capabilities.filter((c) => !c.connected && c.connectUrl);
73
+ const capabilityList = disconnected.length > 0 ? disconnected.map((c) => c.name).join(", ") : "none";
74
+ return {
75
+ 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.`,
76
+ parameters: {
77
+ type: "object",
78
+ properties: {
79
+ capability_name: {
80
+ type: "string",
81
+ description: "The name of the capability to connect"
82
+ }
83
+ },
84
+ required: ["capability_name"]
85
+ },
86
+ execute: async (...args) => {
87
+ const input = args[0] ?? {};
88
+ const name = input.capability_name ?? "";
89
+ if (!name && disconnected.length === 1 && disconnected[0]) {
90
+ return `${disconnected[0].name} requires authorization. Please visit the following link to connect: ${disconnected[0].connectUrl}`;
91
+ }
92
+ const match = disconnected.find(
93
+ (c) => c.name.toLowerCase() === name.toLowerCase()
94
+ );
95
+ if (!match) {
96
+ const isConnected = capabilities.find(
97
+ (c) => c.connected && c.name.toLowerCase() === name.toLowerCase()
98
+ );
99
+ if (isConnected) {
100
+ return `${isConnected.name} is already connected and ready to use.`;
101
+ }
102
+ return `Unknown capability "${name}". Available disconnected capabilities: ${capabilityList}.`;
103
+ }
104
+ return `${match.name} requires authorization. Please visit the following link to connect: ${match.connectUrl}`;
105
+ }
106
+ };
107
+ }
108
+
109
+ // src/adapters/ai/index.ts
110
+ async function toKontextTools(client, options) {
111
+ const [tools, integrations] = await Promise.all([
112
+ client.tools.list(),
113
+ client.integrations.list()
114
+ ]);
115
+ const coreTools = {};
116
+ const usedNames = /* @__PURE__ */ new Set();
117
+ for (const tool of tools) {
118
+ const name = buildToolName(tool, usedNames);
119
+ coreTools[name] = {
120
+ description: formatDescription(tool),
121
+ parameters: tool.inputSchema ? jsonSchema(tool.inputSchema) : jsonSchema({ type: "object", properties: {} }),
122
+ execute: async (args) => {
123
+ const r = await client.tools.execute(tool.id, args);
124
+ return (options?.formatResult ?? defaultFormat)(r);
125
+ }
126
+ };
127
+ }
128
+ const { systemPrompt, requestCapabilityTool } = enrichToolsWithAuthAwareness(
129
+ tools.map((t) => t.name),
130
+ integrations
131
+ );
132
+ if (requestCapabilityTool) {
133
+ coreTools[requestCapabilityTool.name] = {
134
+ description: requestCapabilityTool.description,
135
+ parameters: jsonSchema(
136
+ requestCapabilityTool.parameters
137
+ ),
138
+ execute: async (args) => {
139
+ return requestCapabilityTool.execute(args);
140
+ }
141
+ };
142
+ }
143
+ return { tools: coreTools, systemPrompt, integrations };
144
+ }
145
+ function defaultFormat(r) {
146
+ return r.content;
147
+ }
148
+ function formatDescription(tool) {
149
+ const base = tool.description ?? tool.name;
150
+ const serverLabel = tool.server?.name ?? tool.server?.id;
151
+ return serverLabel ? `[${serverLabel}] ${base}` : base;
152
+ }
153
+ function buildToolName(tool, usedNames) {
154
+ const rawName = tool.name || tool.id || "tool";
155
+ const base = tool.server ? `${tool.server.name ?? tool.server.id ?? "server"}_${rawName}` : rawName;
156
+ const sanitized = sanitizeToolName(base);
157
+ const fallback = sanitized || `tool_${usedNames.size + 1}`;
158
+ let name = fallback;
159
+ let suffix = 1;
160
+ while (usedNames.has(name)) {
161
+ name = `${fallback}_${suffix}`;
162
+ suffix += 1;
163
+ }
164
+ usedNames.add(name);
165
+ return name;
166
+ }
167
+ function sanitizeToolName(name) {
168
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
169
+ }
170
+
171
+ export { toKontextTools };
172
+ //# sourceMappingURL=index.js.map
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/client/tool-utils.ts","../../../src/adapters/ai/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Shared utilities for Kontext tool handling.\n * Used by both KontextClient (core) and withKontext (Cloudflare adapter).\n */\n\nimport {\n IntegrationConnectionRequiredError,\n type ElicitationEntry,\n} from \"../errors.js\";\n\n// Re-export for backward compatibility\nexport type { ElicitationEntry } from \"../errors.js\";\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport interface IntegrationStatus {\n readonly id: string;\n readonly name: string;\n readonly connected: boolean;\n readonly connectUrl?: string;\n}\n\n// ============================================================================\n// System prompt\n// ============================================================================\n\n/**\n * Build an LLM system prompt based on available tool names and integration status.\n * Detects `*_SEARCH_TOOLS` / `*_EXECUTE_TOOL` suffixed names from the\n * Cloudflare Agents MCP proxy and generates appropriate guidance.\n */\nexport function buildKontextSystemPrompt(\n toolNames: readonly string[],\n integrations: readonly IntegrationStatus[],\n): string {\n if (toolNames.length === 0) return \"\";\n\n const searchTool = toolNames.find((n) => n.endsWith(\"_SEARCH_TOOLS\"));\n const executeTool = toolNames.find((n) => n.endsWith(\"_EXECUTE_TOOL\"));\n const requestCapabilityTool = toolNames.find((n) =>\n n.endsWith(\"_REQUEST_CAPABILITY\"),\n );\n\n let prompt =\n \"You are a helpful assistant with access to various tools and integrations through Kontext.\\n\" +\n \"You can help users with tasks across their connected services (GitHub, Linear, Slack, etc.).\\n\" +\n \"When a user asks about their services, use the available MCP tools to help them.\\n\" +\n \"The user has already authenticated — tools run as the user with their permissions, including access to private repositories and org resources.\";\n\n if (searchTool && executeTool) {\n prompt +=\n `\\n\\nYou have access to external integrations through Kontext MCP tools:\\n` +\n `- Call \\`${searchTool}\\` first to discover what tools/integrations are available.\\n` +\n `- Then call \\`${executeTool}\\` with the tool_id and arguments to run a specific tool.\\n` +\n `- 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 — the tools already know who they are.\\n` +\n `Always start by searching for tools when the user asks about their connected services.`;\n }\n\n // Append integration status section when there are integrations to report\n if (integrations.length > 0) {\n const connected = integrations.filter((i) => i.connected);\n const disconnected = integrations.filter((i) => !i.connected);\n\n if (disconnected.length > 0) {\n prompt += \"\\n\\n## Integration Status\";\n\n if (connected.length > 0) {\n prompt += `\\n\\nYou already have access to: ${connected.map((i) => i.name).join(\", \")}`;\n }\n\n prompt += `\\n\\nThe following services require user authorization: ${disconnected.map((i) => i.name).join(\", \")}`;\n\n if (requestCapabilityTool) {\n const example = disconnected[0]?.name ?? \"GitHub\";\n prompt +=\n `\\n\\n**IMPORTANT:** When the user requests an action that requires one of these services:` +\n `\\n1. Call the \\`${requestCapabilityTool}\\` tool with \\`capability_name\\` set to the service name (e.g. \"${example}\")` +\n `\\n2. The tool will return an authorization link` +\n `\\n3. Include the link in your response so the user can click it to connect` +\n `\\n4. After the user authorizes, the service will be available on their next message`;\n } else {\n prompt +=\n \"\\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.\";\n }\n }\n }\n\n return prompt;\n}\n\n// ============================================================================\n// Auth-aware toolset orchestrator\n// ============================================================================\n\nexport interface AuthAwareToolset {\n /** System prompt with integration status and REQUEST_CAPABILITY instructions. */\n readonly systemPrompt: string;\n /** REQUEST_CAPABILITY tool to inject, or null if all integrations are connected. */\n readonly requestCapabilityTool: {\n readonly name: string;\n readonly description: string;\n readonly parameters: Record<string, unknown>;\n readonly execute: (...args: unknown[]) => Promise<string>;\n } | null;\n}\n\n/**\n * Enrich a tool set with auth awareness.\n *\n * Given tool names and integration status, produces:\n * - A system prompt that tells the LLM about connected/disconnected integrations\n * - A REQUEST_CAPABILITY tool (if needed) that returns auth URLs as text\n *\n * Both `ai/` and `cloudflare/` adapters use this as their core auth layer.\n */\nexport function enrichToolsWithAuthAwareness(\n toolNames: readonly string[],\n integrations: readonly IntegrationStatus[],\n options?: { prefix?: string },\n): AuthAwareToolset {\n const hasDisconnected = integrations.some(\n (i) => !i.connected && i.connectUrl,\n );\n\n if (!hasDisconnected) {\n return {\n systemPrompt: buildKontextSystemPrompt(toolNames, integrations),\n requestCapabilityTool: null,\n };\n }\n\n // Determine prefix from existing tool names or option\n const prefix =\n options?.prefix ??\n toolNames\n .find((n) => n.endsWith(\"_SEARCH_TOOLS\"))\n ?.replace(/_SEARCH_TOOLS$/, \"\") ??\n \"kontext\";\n\n const toolName = `${prefix}_REQUEST_CAPABILITY`;\n const tool = buildRequestCapabilityTool(integrations);\n\n // Include REQUEST_CAPABILITY in tool names so the system prompt references it\n const allToolNames = [...toolNames, toolName];\n\n return {\n systemPrompt: buildKontextSystemPrompt(allToolNames, integrations),\n requestCapabilityTool: { name: toolName, ...tool },\n };\n}\n\n// ============================================================================\n// Integration status parsing\n// ============================================================================\n\n/**\n * Parse integration status from gateway tool search results.\n * Extracts connected integrations from tools and disconnected from errors.\n */\nexport function parseIntegrationStatus(\n tools: ReadonlyArray<{ server?: { id?: string; name?: string } }>,\n errors: ReadonlyArray<{ serverId: string; serverName?: string }>,\n elicitations?: ReadonlyArray<{\n url: string;\n integrationId?: string;\n integrationName?: string;\n }>,\n): IntegrationStatus[] {\n const seen = new Set<string>();\n const result: IntegrationStatus[] = [];\n\n for (const t of tools) {\n const sid = t.server?.id;\n if (sid && !seen.has(sid)) {\n seen.add(sid);\n result.push({\n id: sid,\n name: t.server?.name ?? sid,\n connected: true,\n });\n }\n }\n\n for (const e of errors) {\n if (!seen.has(e.serverId)) {\n seen.add(e.serverId);\n const elicitation = elicitations?.find(\n (el) => el.integrationId === e.serverId,\n );\n result.push({\n id: e.serverId,\n name: e.serverName ?? e.serverId,\n connected: false,\n connectUrl: elicitation?.url,\n });\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Request capability tool\n// ============================================================================\n\n/**\n * Build a tool that lets the LLM request connection of a disconnected capability.\n * Returns the connect URL as text so the LLM can present it to the user.\n * No popups — the user clicks the link in the chat to authorize.\n */\nexport function buildRequestCapabilityTool(\n capabilities: readonly IntegrationStatus[],\n): {\n description: string;\n parameters: Record<string, unknown>;\n execute: (...args: unknown[]) => Promise<string>;\n} {\n const disconnected = capabilities.filter((c) => !c.connected && c.connectUrl);\n\n const capabilityList =\n disconnected.length > 0\n ? disconnected.map((c) => c.name).join(\", \")\n : \"none\";\n\n return {\n description:\n `Request connection to a capability that is not yet connected. ` +\n `Currently disconnected capabilities: ${capabilityList}. ` +\n `Call this tool with the capability name to initiate the connection flow.`,\n parameters: {\n type: \"object\",\n properties: {\n capability_name: {\n type: \"string\",\n description: \"The name of the capability to connect\",\n },\n },\n required: [\"capability_name\"],\n },\n execute: async (...args: unknown[]): Promise<string> => {\n const input = (args[0] ?? {}) as { capability_name?: string };\n const name = input.capability_name ?? \"\";\n\n // Auto-select when called without a name and there's only one option\n if (!name && disconnected.length === 1 && disconnected[0]) {\n return (\n `${disconnected[0].name} requires authorization. ` +\n `Please visit the following link to connect: ${disconnected[0].connectUrl}`\n );\n }\n\n const match = disconnected.find(\n (c) => c.name.toLowerCase() === name.toLowerCase(),\n );\n\n if (!match) {\n const isConnected = capabilities.find(\n (c) => c.connected && c.name.toLowerCase() === name.toLowerCase(),\n );\n if (isConnected) {\n return `${isConnected.name} is already connected and ready to use.`;\n }\n return `Unknown capability \"${name}\". Available disconnected capabilities: ${capabilityList}.`;\n }\n\n return (\n `${match.name} requires authorization. ` +\n `Please visit the following link to connect: ${match.connectUrl}`\n );\n },\n };\n}\n\n/**\n * Pre-flight integration status detection.\n * Calls SEARCH_TOOLS on raw (unwrapped) tools to detect connected/disconnected\n * integrations without triggering any popups or broadcasts.\n */\nexport async function preflightIntegrationStatus(\n tools: ToolSetLike,\n): Promise<IntegrationStatus[]> {\n const searchToolKey = Object.keys(tools).find((k) =>\n k.endsWith(\"_SEARCH_TOOLS\"),\n );\n if (!searchToolKey || !tools[searchToolKey]?.execute) return [];\n\n try {\n const result = await tools[searchToolKey].execute!({ limit: 100 });\n\n // Cloudflare getAITools() returns MCP CallToolResult objects, not strings.\n // Extract JSON text from either a plain string or a CallToolResult shape.\n let json: string | undefined;\n if (typeof result === \"string\") {\n json = result;\n } else if (result && typeof result === \"object\") {\n const content = (\n result as {\n content?: Array<{\n type: string;\n resource?: { text?: string };\n text?: string;\n }>;\n }\n ).content;\n const first = content?.[0];\n json = first?.resource?.text ?? first?.text;\n }\n\n if (typeof json === \"string\") {\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === \"object\") {\n return parseIntegrationStatus(\n Array.isArray(parsed.items) ? parsed.items : [],\n Array.isArray(parsed.errors) ? parsed.errors : [],\n Array.isArray(parsed.elicitations) ? parsed.elicitations : [],\n );\n }\n }\n } catch (err: unknown) {\n // All integrations disconnected: -32042 error contains elicitations\n const elicitations = extractElicitations(err);\n if (elicitations?.length) {\n return elicitations\n .filter(\n (e): e is ElicitationEntry & { integrationId: string } =>\n !!e.integrationId,\n )\n .map((e) => ({\n id: e.integrationId,\n name: e.integrationName ?? e.integrationId,\n connected: false,\n connectUrl: e.url,\n }));\n }\n }\n\n return [];\n}\n\n// ============================================================================\n// Tool wrapping\n// ============================================================================\n\n/** A tool with an optional async execute method */\nexport interface ToolLike {\n execute?: (...args: unknown[]) => Promise<unknown>;\n [key: string]: unknown;\n}\n\n/** Record of named tools */\nexport type ToolSetLike = Record<string, ToolLike>;\n\n/**\n * Extract elicitation entries from an error thrown by the MCP SDK.\n * Handles two shapes: direct code/data and nested cause.\n */\nexport function extractElicitations(\n err: unknown,\n): ElicitationEntry[] | undefined {\n const error = err as {\n code?: number;\n data?: { elicitations?: ElicitationEntry[] };\n cause?: {\n code?: number;\n data?: { elicitations?: ElicitationEntry[] };\n };\n };\n\n // Primary: structured McpError with code -32042\n if (error?.code === -32042 && error?.data?.elicitations?.length) {\n return error.data.elicitations;\n }\n\n // Nested cause (some SDK wrappers nest the original error)\n if (\n error?.cause?.code === -32042 &&\n error?.cause?.data?.elicitations?.length\n ) {\n return error.cause.data.elicitations;\n }\n\n return undefined;\n}\n\n/**\n * Wrap tools to catch MCP error code -32042 (URL elicitation required)\n * and convert to IntegrationConnectionRequiredError.\n */\nexport function wrapToolsWithElicitation<T extends ToolSetLike>(tools: T): T {\n return Object.fromEntries(\n Object.entries(tools).map(([name, tool]) => [\n name,\n {\n ...tool,\n execute: tool.execute\n ? async (...args: unknown[]) => {\n try {\n return await tool.execute!(...args);\n } catch (err: unknown) {\n const elicitations = extractElicitations(err);\n const elicitation = elicitations?.[0];\n if (elicitation?.url) {\n const integrationId = elicitation.integrationId ?? \"unknown\";\n throw new IntegrationConnectionRequiredError(integrationId, {\n integrationName: elicitation.integrationName,\n connectUrl: elicitation.url,\n message: elicitation.message,\n });\n }\n throw err;\n }\n }\n : undefined,\n },\n ]),\n ) as T;\n}\n","/**\n * Vercel AI SDK adapter for Kontext.\n *\n * Pure format converter: maps KontextClient tools to AI SDK CoreTools.\n *\n * @example\n * ```typescript\n * import { createKontextClient } from '@kontext-dev/js-sdk';\n * import { toKontextTools } from '@kontext-dev/js-sdk/ai';\n * import { generateText } from 'ai';\n *\n * const client = createKontextClient({\n * clientId: process.env.KONTEXT_CLIENT_ID!,\n * redirectUri: 'http://localhost:3000/callback',\n * onAuthRequired: (url) => open(url.toString()),\n * });\n *\n * const { tools, systemPrompt } = await toKontextTools(client);\n * const result = await generateText({ model, system: systemPrompt, tools, prompt: '...', maxSteps: 5 });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { jsonSchema, type CoreTool } from \"ai\";\nimport type {\n KontextClient,\n KontextTool,\n IntegrationInfo,\n ToolResult,\n} from \"../../client/index.js\";\nimport type { KontextOrchestrator } from \"../../client/orchestrator/index.js\";\nimport { enrichToolsWithAuthAwareness } from \"../../client/tool-utils.js\";\n\nexport interface ToKontextToolsOptions {\n formatResult?: (result: ToolResult) => unknown;\n}\n\nexport interface KontextToolsResult {\n readonly tools: Record<string, CoreTool>;\n readonly systemPrompt: string;\n readonly integrations: readonly IntegrationInfo[];\n}\n\n/**\n * Convert a KontextClient's tools into an AI SDK ToolSet with system prompt.\n *\n * Calls `client.tools.list()` and `client.integrations.list()` to discover\n * tools and integration status, then wraps each tool as an AI SDK `CoreTool`\n * whose `execute` delegates to `client.tools.execute()`.\n */\nexport async function toKontextTools(\n client: KontextClient | KontextOrchestrator,\n options?: ToKontextToolsOptions,\n): Promise<KontextToolsResult> {\n const [tools, integrations] = await Promise.all([\n client.tools.list(),\n client.integrations.list(),\n ]);\n\n const coreTools: Record<string, CoreTool> = {};\n const usedNames = new Set<string>();\n\n for (const tool of tools) {\n const name = buildToolName(tool, usedNames);\n coreTools[name] = {\n description: formatDescription(tool),\n parameters: tool.inputSchema\n ? jsonSchema(tool.inputSchema as Parameters<typeof jsonSchema>[0])\n : jsonSchema({ type: \"object\" as const, properties: {} }),\n execute: async (args: Record<string, unknown>) => {\n const r = await client.tools.execute(tool.id, args);\n return (options?.formatResult ?? defaultFormat)(r);\n },\n };\n }\n\n const { systemPrompt, requestCapabilityTool } = enrichToolsWithAuthAwareness(\n tools.map((t) => t.name),\n integrations,\n );\n\n if (requestCapabilityTool) {\n coreTools[requestCapabilityTool.name] = {\n description: requestCapabilityTool.description,\n parameters: jsonSchema(\n requestCapabilityTool.parameters as Parameters<typeof jsonSchema>[0],\n ),\n execute: async (args: Record<string, unknown>) => {\n return requestCapabilityTool.execute(args);\n },\n };\n }\n\n return { tools: coreTools, systemPrompt, integrations };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultFormat(r: ToolResult): unknown {\n return r.content;\n}\n\nfunction formatDescription(tool: KontextTool): string {\n const base = tool.description ?? tool.name;\n const serverLabel = tool.server?.name ?? tool.server?.id;\n return serverLabel ? `[${serverLabel}] ${base}` : base;\n}\n\nfunction buildToolName(tool: KontextTool, usedNames: Set<string>): string {\n const rawName = tool.name || tool.id || \"tool\";\n const base = tool.server\n ? `${tool.server.name ?? tool.server.id ?? \"server\"}_${rawName}`\n : rawName;\n const sanitized = sanitizeToolName(base);\n const fallback = sanitized || `tool_${usedNames.size + 1}`;\n\n let name = fallback;\n let suffix = 1;\n while (usedNames.has(name)) {\n name = `${fallback}_${suffix}`;\n suffix += 1;\n }\n\n usedNames.add(name);\n return name;\n}\n\nfunction sanitizeToolName(name: string): string {\n return name\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 64);\n}\n"]}