UNPKG

@mondaydotcomorg/atp-client

Version:
1,568 lines (1,557 loc) 50.7 kB
'use strict'; var atpProtocol = require('@mondaydotcomorg/atp-protocol'); var atpRuntime = require('@mondaydotcomorg/atp-runtime'); var zod = require('zod'); var zodToJsonSchema = require('zod-to-json-schema'); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/core/session.ts var ClientSession = class { static { __name(this, "ClientSession"); } baseUrl; customHeaders; clientId; clientToken; initPromise; hooks; constructor(baseUrl, headers, hooks) { this.baseUrl = baseUrl; this.customHeaders = headers || {}; this.hooks = hooks; } /** * Initializes the client session with the server. * This MUST be called before any other operations. * The server generates and returns a unique client ID and token. * @param clientInfo - Optional client information * @param tools - Optional client tool definitions to register with the server * @param services - Optional client service capabilities (LLM, approval, embedding) */ async init(clientInfo, tools, services) { if (this.initPromise) { await this.initPromise; return { clientId: this.clientId, token: this.clientToken, expiresAt: 0, tokenRotateAt: 0 }; } this.initPromise = (async () => { const url = `${this.baseUrl}/api/init`; const body = JSON.stringify({ clientInfo, tools: tools || [], services }); const headers = await this.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); if (!response.ok) { throw new Error(`Client initialization failed: ${response.status} ${response.statusText}`); } const data = await response.json(); this.clientId = data.clientId; this.clientToken = data.token; })(); await this.initPromise; return { clientId: this.clientId, token: this.clientToken, expiresAt: 0, tokenRotateAt: 0 }; } /** * Gets the unique client ID. */ getClientId() { if (!this.clientId) { throw new Error("Client not initialized. Call init() first."); } return this.clientId; } /** * Ensures the client is initialized before making requests. */ async ensureInitialized() { if (!this.clientId) { throw new Error("Client not initialized. Call init() first."); } } /** * Creates HTTP headers for requests. */ getHeaders() { const headers = { "Content-Type": "application/json", ...this.customHeaders }; if (this.clientId) { headers["X-Client-ID"] = this.clientId; } if (this.clientToken) { headers["Authorization"] = `Bearer ${this.clientToken}`; } return headers; } getBaseUrl() { return this.baseUrl; } /** * Updates the client token from response headers (token refresh). */ updateToken(response) { const newToken = response.headers.get("X-ATP-Token"); if (newToken) { this.clientToken = newToken; } } /** * Prepares headers for a request, calling preRequest hook if configured */ async prepareHeaders(method, url, body) { let headers = { "Content-Type": "application/json", ...this.customHeaders }; if (this.clientId) { headers["X-Client-ID"] = this.clientId; } if (this.clientToken) { headers["Authorization"] = `Bearer ${this.clientToken}`; } if (this.hooks?.preRequest) { try { const result = await this.hooks.preRequest({ url, method, currentHeaders: headers, body }); if (result.abort) { throw new Error(result.abortReason || "Request aborted by preRequest hook"); } if (result.headers) { headers = result.headers; } } catch (error) { throw error; } } return headers; } }; // src/core/in-process-session.ts var InProcessSession = class { static { __name(this, "InProcessSession"); } server; clientId; clientToken; initialized = false; initPromise; constructor(server) { this.server = server; } async init(clientInfo, tools, services) { if (this.initPromise) { await this.initPromise; return { clientId: this.clientId, token: this.clientToken, expiresAt: 0, tokenRotateAt: 0 }; } this.initPromise = (async () => { await this.server.start(); const ctx = this.createContext({ method: "POST", path: "/api/init", body: { clientInfo, tools: tools || [], services } }); const result = await this.server.handleInit(ctx); this.clientId = result.clientId; this.clientToken = result.token; this.initialized = true; })(); await this.initPromise; return { clientId: this.clientId, token: this.clientToken, expiresAt: 0, tokenRotateAt: 0 }; } getClientId() { if (!this.clientId) { throw new Error("Client not initialized. Call init() first."); } return this.clientId; } async ensureInitialized() { if (!this.initialized) { throw new Error("Client not initialized. Call init() first."); } } getHeaders() { const headers = { "content-type": "application/json" }; if (this.clientId) { headers["x-client-id"] = this.clientId; } if (this.clientToken) { headers["authorization"] = `Bearer ${this.clientToken}`; } return headers; } getBaseUrl() { return ""; } updateToken(_response) { } async prepareHeaders(_method, _url, _body) { return this.getHeaders(); } async getDefinitions(options) { await this.ensureInitialized(); const ctx = this.createContext({ method: "GET", path: "/api/definitions", query: options?.apiGroups ? { apiGroups: options.apiGroups.join(",") } : {} }); return await this.server.getDefinitions(ctx); } async getRuntimeDefinitions(options) { await this.ensureInitialized(); const ctx = this.createContext({ method: "GET", path: "/api/runtime", query: options?.apis?.length ? { apis: options.apis.join(",") } : {} }); return await this.server.getRuntimeDefinitions(ctx); } async getServerInfo() { await this.ensureInitialized(); return this.server.getInfo(); } async search(query, options) { await this.ensureInitialized(); const ctx = this.createContext({ method: "POST", path: "/api/search", body: { query, ...options } }); return await this.server.handleSearch(ctx); } async explore(path) { await this.ensureInitialized(); const ctx = this.createContext({ method: "POST", path: "/api/explore", body: { path } }); return await this.server.handleExplore(ctx); } async execute(code, config) { await this.ensureInitialized(); const ctx = this.createContext({ method: "POST", path: "/api/execute", body: { code, config } }); return await this.server.handleExecute(ctx); } async resume(executionId, callbackResult) { await this.ensureInitialized(); const ctx = this.createContext({ method: "POST", path: `/api/resume/${executionId}`, body: { result: callbackResult } }); return await this.server.handleResume(ctx, executionId); } async resumeWithBatchResults(executionId, batchResults) { await this.ensureInitialized(); const ctx = this.createContext({ method: "POST", path: `/api/resume/${executionId}`, body: { results: batchResults } }); return await this.server.handleResume(ctx, executionId); } createContext(options) { const noopLogger = { debug: /* @__PURE__ */ __name(() => { }, "debug"), info: /* @__PURE__ */ __name(() => { }, "info"), warn: /* @__PURE__ */ __name(() => { }, "warn"), error: /* @__PURE__ */ __name(() => { }, "error") }; return { method: options.method, path: options.path, query: options.query || {}, headers: this.getHeaders(), body: options.body, clientId: this.clientId, clientToken: this.clientToken, logger: noopLogger, status: 200, responseBody: null, throw: /* @__PURE__ */ __name((status, message) => { const error = new Error(message); error.status = status; throw error; }, "throw"), assert: /* @__PURE__ */ __name((condition, message) => { if (!condition) { throw new Error(message); } }, "assert"), set: /* @__PURE__ */ __name(() => { }, "set") }; } }; // src/core/api-operations.ts var APIOperations = class { static { __name(this, "APIOperations"); } session; inProcessSession; apiDefinitions; constructor(session, inProcessSession) { this.session = session; this.inProcessSession = inProcessSession; } /** * Connects to the server and retrieves API definitions. */ async connect(options) { await this.session.ensureInitialized(); if (this.inProcessSession) { const data2 = await this.inProcessSession.getDefinitions(options); this.apiDefinitions = data2.typescript; return { serverVersion: data2.version, capabilities: {}, apiGroups: data2.apiGroups }; } const params = new URLSearchParams(); if (options?.apiGroups) { params.set("apiGroups", options.apiGroups.join(",")); } const url = `${this.session.getBaseUrl()}/api/definitions?${params}`; const headers = await this.session.prepareHeaders("GET", url); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`Connection failed: ${response.status} ${response.statusText}`); } const data = await response.json(); this.apiDefinitions = data.typescript; return { serverVersion: data.version, capabilities: {}, apiGroups: data.apiGroups }; } /** * Gets the TypeScript type definitions for available APIs. */ getTypeDefinitions() { if (!this.apiDefinitions) { throw new Error("Not connected. Call connect() first."); } return this.apiDefinitions; } /** * Searches for available API functions. */ async searchAPI(query, options) { await this.session.ensureInitialized(); if (this.inProcessSession) { const data2 = await this.inProcessSession.search(query, options); return data2.results; } const url = `${this.session.getBaseUrl()}/api/search`; const body = JSON.stringify({ query, ...options }); const headers = await this.session.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); if (!response.ok) { throw new Error(`Search failed: ${response.status} ${response.statusText}`); } const data = await response.json(); return data.results; } /** * Explores the API filesystem at the given path. */ async exploreAPI(path) { await this.session.ensureInitialized(); if (this.inProcessSession) { return await this.inProcessSession.explore(path); } const url = `${this.session.getBaseUrl()}/api/explore`; const body = JSON.stringify({ path }); const headers = await this.session.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); if (!response.ok) { throw new Error(`Explore failed: ${response.status} ${response.statusText}`); } return await response.json(); } /** * Gets information about the server. */ async getServerInfo() { await this.session.ensureInitialized(); if (this.inProcessSession) { return await this.inProcessSession.getServerInfo(); } const url = `${this.session.getBaseUrl()}/api/info`; const headers = await this.session.prepareHeaders("GET", url); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`Failed to get server info: ${response.status}`); } return await response.json(); } /** * Gets ATP runtime API definitions as TypeScript declarations. * Returns the full TypeScript definitions for atp.llm.*, atp.cache.*, etc. * These are the APIs available during code execution. * * Behavior: * - No options: Returns APIs based on client capabilities (default filtering) * - apis: ['llm', 'cache']: Returns only specified APIs (intersection with client capabilities) * - apis: []: Returns all APIs regardless of client capabilities * * @param options - Optional filtering options * @param options.apis - Specific APIs to include (e.g., ['llm', 'cache', 'approval']) */ async getRuntimeDefinitions(options) { await this.session.ensureInitialized(); if (this.inProcessSession) { return await this.inProcessSession.getRuntimeDefinitions(options?.apis ? { apis: options.apis } : void 0); } const params = new URLSearchParams(); if (options?.apis && options.apis.length > 0) { params.set("apis", options.apis.join(",")); } const url = `${this.session.getBaseUrl()}/api/runtime${params.toString() ? `?${params}` : ""}`; const headers = await this.session.prepareHeaders("GET", url); const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`Failed to get runtime definitions: ${response.status}`); } return await response.text(); } }; // src/errors.ts var ClientCallbackError = class extends Error { static { __name(this, "ClientCallbackError"); } constructor(message) { super(message); this.name = "ClientCallbackError"; } }; // src/core/provenance-registry.ts var ProvenanceTokenRegistry = class { static { __name(this, "ProvenanceTokenRegistry"); } cache = /* @__PURE__ */ new Map(); maxSize; ttl; sequenceCounter = 0; constructor(maxSize = 1e4, ttlHours = 1) { this.maxSize = maxSize; this.ttl = ttlHours * 3600 * 1e3; } /** * Add a token to the registry */ add(token) { this.evictExpired(); if (this.cache.size >= this.maxSize) { this.evictLRU(); } this.cache.set(token, { token, addedAt: Date.now(), sequence: this.sequenceCounter++ }); } /** * Get recent tokens (non-expired, sorted by age, limited) * Returns tokens in chronological order (oldest first, most recent last) */ getRecentTokens(maxCount = 1e3) { if (maxCount <= 0) { return []; } this.evictExpired(); const now = Date.now(); const expiredTokens = []; const entries = Array.from(this.cache.values()).filter((entry) => { try { const [body] = entry.token.split("."); if (!body) { expiredTokens.push(entry.token); return false; } const payload = JSON.parse(Buffer.from(body, "base64url").toString()); if (!payload.expiresAt || payload.expiresAt <= now) { expiredTokens.push(entry.token); return false; } return true; } catch { expiredTokens.push(entry.token); return false; } }).sort((a, b) => a.sequence - b.sequence).slice(-maxCount); for (const token of expiredTokens) { this.cache.delete(token); } return entries.map((e) => e.token); } /** * Clear all tokens */ clear() { this.cache.clear(); } /** * Get registry size */ size() { return this.cache.size; } /** * Evict expired tokens */ evictExpired() { const now = Date.now(); const toDelete = []; for (const [token, entry] of this.cache.entries()) { if (now - entry.addedAt > this.ttl) { toDelete.push(token); } } for (const token of toDelete) { this.cache.delete(token); } } /** * Evict least recently used (oldest) token */ evictLRU() { let oldestToken = null; let oldestSequence = Infinity; for (const [token, entry] of this.cache.entries()) { if (entry.sequence < oldestSequence) { oldestSequence = entry.sequence; oldestToken = token; } } if (oldestToken) { this.cache.delete(oldestToken); } } }; // src/core/execution-operations.ts var ExecutionOperations = class { static { __name(this, "ExecutionOperations"); } session; inProcessSession; serviceProviders; tokenRegistry; lastExecutionConfig = null; constructor(session, serviceProviders, inProcessSession) { this.session = session; this.inProcessSession = inProcessSession; this.serviceProviders = serviceProviders; this.tokenRegistry = new ProvenanceTokenRegistry(); } /** * Executes code on the server with real-time progress updates via SSE. */ async executeStream(code, config, onProgress) { await this.session.ensureInitialized(); const url = `${this.session.getBaseUrl()}/api/execute/stream`; const body = JSON.stringify({ code, config }); const headers = await this.session.prepareHeaders("POST", url, body); return new Promise((resolve, reject) => { const fetchImpl = typeof fetch !== "undefined" ? fetch : __require("undici").fetch; fetchImpl(url, { method: "POST", headers, body }).then(async (response) => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const reader = response.body?.getReader(); if (!reader) { throw new Error("Response body is not readable"); } const decoder = new TextDecoder(); let buffer = ""; let result = null; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line && line.startsWith("event:")) { const event = line.substring(6).trim(); for (let j = i + 1; j < lines.length; j++) { const dataLine = lines[j]; if (dataLine && dataLine.startsWith("data:")) { const dataStr = dataLine.substring(5).trim(); if (dataStr) { try { const data = JSON.parse(dataStr); if (event === "progress" && onProgress) { onProgress(data.message, data.fraction); } else if (event === "result") { result = data; } else if (event === "error") { reject(new Error(data.message)); return; } } catch (e) { atpRuntime.log.error("Failed to parse SSE data", { dataStr, error: e }); } } break; } } } } } if (result) { resolve(result); } else { reject(new Error("No result received from server")); } }).catch(reject); }); } /** * Executes code on the server in a sandboxed environment. */ async execute(code, config) { await this.session.ensureInitialized(); const hints = this.tokenRegistry.getRecentTokens(1e3); const detectedClientServices = { hasLLM: !!this.serviceProviders.getLLM(), hasApproval: !!this.serviceProviders.getApproval(), hasEmbedding: !!this.serviceProviders.getEmbedding(), hasTools: this.serviceProviders.hasTools() }; const executionConfig = { ...config, clientServices: { ...detectedClientServices, ...config?.clientServices || {} }, provenanceHints: hints.length > 0 ? hints : void 0 }; this.lastExecutionConfig = executionConfig; let result; if (this.inProcessSession) { result = await this.inProcessSession.execute(code, executionConfig); } else { const url = `${this.session.getBaseUrl()}/api/execute`; const body = JSON.stringify({ code, config: executionConfig }); const headers = await this.session.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); this.session.updateToken(response); if (!response.ok) { const error = await response.json(); throw new Error(`Execution failed: ${error.error || response.statusText}`); } result = await response.json(); } if (result.provenanceTokens && result.provenanceTokens.length > 0) { for (const { token } of result.provenanceTokens) { this.tokenRegistry.add(token); } } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallbacks) { return await this.handleBatchCallbacksAndResume(result); } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallback) { return await this.handlePauseAndResume(result); } return result; } /** * Handles batch callbacks by executing them in parallel and resuming. */ async handleBatchCallbacksAndResume(pausedResult) { if (!pausedResult.needsCallbacks || pausedResult.needsCallbacks.length === 0) { throw new Error("No batch callback requests in paused execution"); } const missingServiceIds = new Set(pausedResult.needsCallbacks.filter((cb) => !this.serviceProviders.hasServiceForCallback(cb.type)).map((cb) => cb.id)); if (missingServiceIds.size > 0) { const missingServices = pausedResult.needsCallbacks.filter((cb) => missingServiceIds.has(cb.id)); const explicitlyRequestedMissing = missingServices.filter((cb) => this.wasServiceExplicitlyRequested(cb.type)); const unexpectedMissing = missingServices.filter((cb) => !this.wasServiceExplicitlyRequested(cb.type)); if (explicitlyRequestedMissing.length > 0) { return pausedResult; } const errorMessage = `Missing service providers for callback types: ${unexpectedMissing.map((cb) => cb.type).join(", ")}`; atpRuntime.log.error(`Auto-handling batch paused execution without service providers: ${errorMessage}`, { executionId: pausedResult.executionId, missingServices: unexpectedMissing.map((cb) => ({ type: cb.type, operation: cb.operation, id: cb.id })) }); const existingCallbacks = pausedResult.needsCallbacks.filter((cb) => !missingServiceIds.has(cb.id)); if (existingCallbacks.length > 0) { try { const existingResults = await Promise.all(existingCallbacks.map(async (cb) => { const callbackResult = await this.serviceProviders.handleCallback(cb.type, { ...cb.payload, operation: cb.operation }); return { id: cb.id, result: callbackResult }; })); const allResults = pausedResult.needsCallbacks.map((cb) => { if (missingServiceIds.has(cb.id)) { return { id: cb.id, result: { __error: true, message: `${cb.type} service not provided by client` } }; } return existingResults.find((r) => r.id === cb.id); }); return await this.resumeWithBatchResults(pausedResult.executionId, allResults); } catch (error) { const errorMessage2 = error instanceof Error ? error.message : String(error); atpRuntime.log.error(`Error handling existing services in batch: ${errorMessage2}`, { executionId: pausedResult.executionId }); const allErrorResults = pausedResult.needsCallbacks.map((cb) => ({ id: cb.id, result: { __error: true, message: missingServiceIds.has(cb.id) ? `${cb.type} service not provided by client` : errorMessage2 } })); return await this.resumeWithBatchResults(pausedResult.executionId, allErrorResults); } } else { const allErrorResults = pausedResult.needsCallbacks.map((cb) => ({ id: cb.id, result: { __error: true, message: `${cb.type} service not provided by client` } })); return await this.resumeWithBatchResults(pausedResult.executionId, allErrorResults); } } try { const batchResults = await Promise.all(pausedResult.needsCallbacks.map(async (cb) => { const callbackResult = await this.serviceProviders.handleCallback(cb.type, { ...cb.payload, operation: cb.operation }); return { id: cb.id, result: callbackResult }; })); return await this.resumeWithBatchResults(pausedResult.executionId, batchResults); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); atpRuntime.log.error(`Error handling batch callbacks: ${errorMessage}`, { executionId: pausedResult.executionId, callbackCount: pausedResult.needsCallbacks.length }); const allErrorResults = pausedResult.needsCallbacks.map((cb) => ({ id: cb.id, result: { __error: true, message: errorMessage } })); return await this.resumeWithBatchResults(pausedResult.executionId, allErrorResults); } } /** * Handles a paused execution by processing the callback and resuming. */ async handlePauseAndResume(pausedResult) { if (!pausedResult.needsCallback) { throw new Error("No callback request in paused execution"); } if (!this.serviceProviders.hasServiceForCallback(pausedResult.needsCallback.type)) { const wasExplicitlyRequested = this.wasServiceExplicitlyRequested(pausedResult.needsCallback.type); if (wasExplicitlyRequested) { return pausedResult; } const errorMessage = `${pausedResult.needsCallback.type} service not provided by client`; atpRuntime.log.error(`Auto-handling paused execution without service provider: ${errorMessage}`, { executionId: pausedResult.executionId, callbackType: pausedResult.needsCallback.type, operation: pausedResult.needsCallback.operation }); return await this.resume(pausedResult.executionId, { __error: true, message: errorMessage }); } try { const callbackResult = await this.serviceProviders.handleCallback(pausedResult.needsCallback.type, { ...pausedResult.needsCallback.payload, operation: pausedResult.needsCallback.operation, executionId: pausedResult.executionId }); return await this.resume(pausedResult.executionId, callbackResult); } catch (error) { if (error instanceof ClientCallbackError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); atpRuntime.log.error(`Error handling callback: ${errorMessage}`, { executionId: pausedResult.executionId, callbackType: pausedResult.needsCallback.type, operation: pausedResult.needsCallback.operation }); return await this.resume(pausedResult.executionId, { __error: true, message: errorMessage }); } } /** * Check if a service was explicitly requested in clientServices config */ wasServiceExplicitlyRequested(callbackType) { if (!this.lastExecutionConfig?.clientServices) { return false; } switch (callbackType) { case atpProtocol.CallbackType.LLM: return this.lastExecutionConfig.clientServices.hasLLM; case atpProtocol.CallbackType.APPROVAL: return this.lastExecutionConfig.clientServices.hasApproval; case atpProtocol.CallbackType.EMBEDDING: return this.lastExecutionConfig.clientServices.hasEmbedding; case atpProtocol.CallbackType.TOOL: return this.lastExecutionConfig.clientServices.hasTools; default: return false; } } /** * Resumes a paused execution with a callback result. */ async resume(executionId, callbackResult) { await this.session.ensureInitialized(); let result; if (this.inProcessSession) { result = await this.inProcessSession.resume(executionId, callbackResult); } else { const url = `${this.session.getBaseUrl()}/api/resume/${executionId}`; const body = JSON.stringify({ result: callbackResult }); const headers = await this.session.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); this.session.updateToken(response); if (!response.ok) { const error = await response.json(); throw new Error(`Resume failed: ${error.error || response.statusText}`); } result = await response.json(); } if (result.provenanceTokens && result.provenanceTokens.length > 0) { for (const { token } of result.provenanceTokens) { this.tokenRegistry.add(token); } } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallbacks) { return await this.handleBatchCallbacksAndResume(result); } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallback) { return await this.handlePauseAndResume(result); } return result; } /** * Resumes a paused execution with batch callback results. */ async resumeWithBatchResults(executionId, batchResults) { await this.session.ensureInitialized(); let result; if (this.inProcessSession) { result = await this.inProcessSession.resumeWithBatchResults(executionId, batchResults); } else { const url = `${this.session.getBaseUrl()}/api/resume/${executionId}`; const body = JSON.stringify({ results: batchResults }); const headers = await this.session.prepareHeaders("POST", url, body); const response = await fetch(url, { method: "POST", headers, body }); this.session.updateToken(response); if (!response.ok) { const error = await response.json(); throw new Error(`Batch resume failed: ${error.error || response.statusText}`); } result = await response.json(); } if (result.provenanceTokens && result.provenanceTokens.length > 0) { for (const { token } of result.provenanceTokens) { this.tokenRegistry.add(token); } } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallbacks) { return await this.handleBatchCallbacksAndResume(result); } if (result.status === atpProtocol.ExecutionStatus.PAUSED && result.needsCallback) { return await this.handlePauseAndResume(result); } return result; } }; var LLMOperation = { CALL: "call", EXTRACT: "extract", CLASSIFY: "classify" }; var EmbeddingOperation = { EMBED: "embed", SEARCH: "search" }; var ServiceProviders = class { static { __name(this, "ServiceProviders"); } providers = {}; toolHandlers = /* @__PURE__ */ new Map(); constructor(providers) { this.providers = providers || {}; if (providers?.tools) { for (const tool of providers.tools) { this.toolHandlers.set(tool.name, tool.handler); } } } provideLLM(handler) { this.providers.llm = handler; } provideApproval(handler) { this.providers.approval = handler; } provideEmbedding(handler) { this.providers.embedding = handler; } provideTools(tools) { this.providers.tools = tools; for (const tool of tools) { this.toolHandlers.set(tool.name, tool.handler); } } getLLM() { return this.providers.llm; } getApproval() { return this.providers.approval; } getEmbedding() { return this.providers.embedding; } getTools() { return this.providers.tools; } /** * Get tool definitions (without handlers) for sending to server */ getToolDefinitions() { if (!this.providers.tools) { return []; } return this.providers.tools.map((tool) => { const { handler, ...definition } = tool; return definition; }); } /** * Check if client has tools */ hasTools() { return !!(this.providers.tools && this.providers.tools.length > 0); } /** * Check if client has any services or tools */ hasAnyServices() { return !!(this.providers.llm || this.providers.approval || this.providers.embedding || this.hasTools()); } /** * Check if client has a service for a specific callback type */ hasServiceForCallback(callbackType) { switch (callbackType) { case atpProtocol.CallbackType.LLM: return !!this.providers.llm; case atpProtocol.CallbackType.APPROVAL: return !!this.providers.approval; case atpProtocol.CallbackType.EMBEDDING: return !!this.providers.embedding; case atpProtocol.CallbackType.TOOL: return this.hasTools(); default: return false; } } async handleCallback(callbackType, payload) { if (payload.operation === "batch_parallel" && payload.calls) { return await Promise.all(payload.calls.map(async (call) => { return await this.handleCallback(call.type, { ...call.payload, operation: call.operation }); })); } switch (callbackType) { case atpProtocol.CallbackType.LLM: if (!this.providers.llm) { throw new Error("LLM service not provided by client"); } if (payload.operation === LLMOperation.CALL) { return await this.providers.llm.call(payload.prompt, payload.options); } else if (payload.operation === LLMOperation.EXTRACT && this.providers.llm.extract) { return await this.providers.llm.extract(payload.prompt, payload.schema, payload.options); } else if (payload.operation === LLMOperation.CLASSIFY && this.providers.llm.classify) { return await this.providers.llm.classify(payload.text, payload.categories, payload.options); } throw new Error(`Unsupported LLM operation: ${payload.operation}`); case atpProtocol.CallbackType.APPROVAL: if (!this.providers.approval) { throw new Error("Approval service not provided by client"); } const contextWithExecutionId = payload.context ? { ...payload.context, executionId: payload.executionId } : { executionId: payload.executionId }; return await this.providers.approval.request(payload.message, contextWithExecutionId); case atpProtocol.CallbackType.EMBEDDING: if (!this.providers.embedding) { throw new Error("Embedding service not provided by client"); } if (payload.operation === EmbeddingOperation.EMBED) { return await this.providers.embedding.embed(payload.text); } else if (payload.operation === EmbeddingOperation.SEARCH) { const queryEmbedding = await this.providers.embedding.embed(payload.query); return queryEmbedding; } else if (payload.operation === "similarity" && this.providers.embedding.similarity) { return await this.providers.embedding.similarity(payload.text1, payload.text2); } throw new Error(`Unsupported embedding operation: ${payload.operation}`); case atpProtocol.CallbackType.TOOL: if (payload.operation === atpProtocol.ToolOperation.CALL) { const toolName = payload.toolName; const handler = this.toolHandlers.get(toolName); if (!handler) { throw new Error(`Tool '${toolName}' not found in client tools`); } const result = await handler(payload.input); return result; } throw new Error(`Unsupported tool operation: ${payload.operation}`); default: throw new Error(`Unknown callback type: ${callbackType}`); } } }; // src/tools/types.ts var ToolNames = { SEARCH_API: "search_api", FETCH_ALL_APIS: "fetch_all_apis", EXECUTE_CODE: "execute_code", EXPLORE_API: "explore_api" }; var searchApiInputSchema = zod.z.object({ query: zod.z.string().describe("Search query string") }); function createSearchApiTool(client) { return { name: ToolNames.SEARCH_API, description: 'Search for APIs by keyword. Provide search term as string like "add", "math", "user", etc.', inputSchema: zodToJsonSchema.zodToJsonSchema(searchApiInputSchema), zodSchema: searchApiInputSchema, func: /* @__PURE__ */ __name(async (input) => { try { const results = await client.searchAPI(input.query); return JSON.stringify({ success: true, results: results.map((r) => ({ apiGroup: r.apiGroup, functionName: r.functionName, description: r.description, signature: r.signature })), count: results.length }, null, 2); } catch (error) { return JSON.stringify({ success: false, error: error.message }, null, 2); } }, "func") }; } __name(createSearchApiTool, "createSearchApiTool"); var fetchAllApisInputSchema = zod.z.object({ apiGroups: zod.z.array(zod.z.string()).optional().describe("Optional: Specific API groups to include") }); function createFetchAllApisTool(client) { return { name: ToolNames.FETCH_ALL_APIS, description: "Get TypeScript definitions of all available APIs. Returns code showing api.add, api.getTodo, etc.", inputSchema: zodToJsonSchema.zodToJsonSchema(fetchAllApisInputSchema), zodSchema: fetchAllApisInputSchema, func: /* @__PURE__ */ __name(async (_input) => { try { const typescript = client.getTypeDefinitions(); return JSON.stringify({ success: true, typescript, message: "Use this TypeScript to understand available api.* functions" }, null, 2); } catch (error) { return JSON.stringify({ success: false, error: error.message }, null, 2); } }, "func") }; } __name(createFetchAllApisTool, "createFetchAllApisTool"); var executeCodeInputSchema = zod.z.object({ code: zod.z.string().describe("The JavaScript/TypeScript code to execute"), timeout: zod.z.number().optional().describe("Execution timeout in milliseconds (default: 30000)"), maxMemory: zod.z.number().optional().describe("Maximum memory in bytes (default: 128MB)") }); function createExecuteCodeTool(client) { return { name: ToolNames.EXECUTE_CODE, description: "Execute JavaScript/TypeScript code to call APIs. IMPORTANT: Code MUST use 'return' statement to see results. Examples: 'return api.groupName.functionName({})' or 'const result = api.group.func({}); return result'. Use bracket notation for dynamic names: api['groupName']['functionName']({}).", inputSchema: zodToJsonSchema.zodToJsonSchema(executeCodeInputSchema), zodSchema: executeCodeInputSchema, func: /* @__PURE__ */ __name(async (input) => { try { const result = await client.execute(input.code, { timeout: input.timeout, maxMemory: input.maxMemory }); if (result.status === atpProtocol.ExecutionStatus.COMPLETED) { return JSON.stringify({ success: true, result: result.result, stats: { duration: result.stats.duration, memoryUsed: result.stats.memoryUsed } }, null, 2); } else if (result.status === atpProtocol.ExecutionStatus.FAILED) { return JSON.stringify({ success: false, error: result.error?.message || "Execution failed", stack: result.error?.stack, message: "Code execution failed. Check syntax and fix errors." }, null, 2); } else { return JSON.stringify({ success: false, error: "Execution timed out", message: "Code took too long. Simplify or optimize." }, null, 2); } } catch (error) { return JSON.stringify({ success: false, error: error.message, message: "Failed to execute code" }, null, 2); } }, "func") }; } __name(createExecuteCodeTool, "createExecuteCodeTool"); var exploreApiInputSchema = zod.z.object({ path: zod.z.string().describe('Path to explore (e.g., "/", "/openapi/github", "/mcp/filesystem/read_file")') }); function createExploreApiTool(client) { return { name: ToolNames.EXPLORE_API, description: 'Explore APIs using filesystem-like navigation. Navigate through directories to discover available functions. Provide path as string like "/", "/openapi", "/openapi/github", or "/openapi/github/repos/createRepo" to see functions.', inputSchema: zodToJsonSchema.zodToJsonSchema(exploreApiInputSchema), zodSchema: exploreApiInputSchema, func: /* @__PURE__ */ __name(async (input) => { try { const result = await client.exploreAPI(input.path); if (result.type === "directory") { return JSON.stringify({ success: true, type: "directory", path: result.path, items: result.items }, null, 2); } else { return JSON.stringify({ success: true, type: "function", name: result.name, description: result.description, definition: result.definition, group: result.group, path: result.path }, null, 2); } } catch (error) { return JSON.stringify({ success: false, error: error.message }, null, 2); } }, "func") }; } __name(createExploreApiTool, "createExploreApiTool"); // src/client.ts var AgentToolProtocolClient = class { static { __name(this, "AgentToolProtocolClient"); } session; inProcessSession; apiOps; execOps; serviceProviders; /** * Creates a new client instance. * * @example * ```typescript * // HTTP mode * const client = new AgentToolProtocolClient({ * baseUrl: 'http://localhost:3333', * headers: { Authorization: 'Bearer token' }, * hooks: { * preRequest: async (context) => { * const token = await refreshToken(); * return { headers: { ...context.currentHeaders, Authorization: `Bearer ${token}` } }; * } * } * }); * * // In-process mode (no port binding) * const server = createServer(); * server.use(myApiGroup); * const client = new AgentToolProtocolClient({ server }); * ``` */ constructor(options) { const { baseUrl, server, headers, serviceProviders, hooks } = options; if (!baseUrl && !server) { throw new Error("Either baseUrl or server must be provided"); } if (baseUrl && server) { throw new Error("Cannot provide both baseUrl and server"); } this.serviceProviders = new ServiceProviders(serviceProviders); if (server) { this.inProcessSession = new InProcessSession(server); this.session = this.inProcessSession; this.apiOps = new APIOperations(this.session, this.inProcessSession); this.execOps = new ExecutionOperations(this.session, this.serviceProviders, this.inProcessSession); } else { this.session = new ClientSession(baseUrl, headers, hooks); this.apiOps = new APIOperations(this.session); this.execOps = new ExecutionOperations(this.session, this.serviceProviders); } } /** * Initializes the client session with the server. * Automatically registers any client-provided tools and services with the server. */ async init(clientInfo) { const toolDefinitions = this.serviceProviders.getToolDefinitions(); const services = { hasLLM: !!this.serviceProviders.getLLM(), hasApproval: !!this.serviceProviders.getApproval(), hasEmbedding: !!this.serviceProviders.getEmbedding(), hasTools: this.serviceProviders.hasTools() }; return await this.session.init(clientInfo, toolDefinitions, services); } /** * Gets the unique client ID. */ getClientId() { return this.session.getClientId(); } /** * Provides an LLM implementation for server to use during execution. */ provideLLM(handler) { this.serviceProviders.provideLLM(handler); } /** * Provides an approval handler for server to request human approval. */ provideApproval(handler) { this.serviceProviders.provideApproval(handler); } /** * Provides an embedding model for server to use. */ provideEmbedding(handler) { this.serviceProviders.provideEmbedding(handler); } /** * Provides custom tools that execute on the client side. * Note: Must be called before init() or re-initialize after calling this. */ provideTools(tools) { this.serviceProviders.provideTools(tools); } /** * Gets all client-provided tools (registered via provideTools). * Returns the full tool objects including handlers. */ getClientTools() { return this.serviceProviders.getTools() || []; } /** * Gets client-provided tool definitions (without handlers). * Useful for sending tool metadata to servers. */ getClientToolDefinitions() { return this.serviceProviders.getToolDefinitions(); } /** * Gets the ATP tools (execute_code, explore_api, search_api, fetch_all_apis). * These are ready-to-use tools that can be exposed to MCP or other frameworks. * * @example * ```typescript * const tools = client.getATPTools(); * for (const tool of tools) { * mcpServer.tool(tool.name, tool.description, tool.inputSchema, async (args) => { * const result = await tool.func(args); * return { content: [{ type: 'text', text: result }] }; * }); * } * ``` */ getATPTools() { return [ createSearchApiTool(this), createFetchAllApisTool(this), createExecuteCodeTool(this), createExploreApiTool(this) ]; } /** * Connects to the server and retrieves API definitions. */ async connect(options) { return await this.apiOps.connect(options); } /** * Gets the TypeScript type definitions for available APIs. */ getTypeDefinitions() { return this.apiOps.getTypeDefinitions(); } /** * Searches for available API functions. */ async searchAPI(query, options) { return await this.apiOps.searchAPI(query, options); } /** * Explores the API filesystem at the given path. */ async exploreAPI(path) { return await this.apiOps.exploreAPI(path); } /** * Executes code on the server with real-time progress updates via SSE. */ async executeStream(code, config, onProgress) { return await this.execOps.executeStream(code, config, onProgress); } /** * Executes code on the server in a sandboxed environment. */ async execute(code, config) { return await this.execOps.execute(code, config); } /** * Resumes a paused execution with a callback result. */ async resume(executionId, callbackResult) { return await this.execOps.resume(executionId, callbackResult); } /** * Handles a callback request from the server during execution. */ async handleCallback(callbackType, payload) { return await this.serviceProviders.handleCallback(callbackType, payload); } /** * Gets information about the server. */ async getServerInfo() { return await this.apiOps.getServerInfo(); } /** * Gets ATP runtime API definitions as TypeScript declarations. * Returns the full TypeScript definitions for atp.llm.*, atp.cache.*, etc. * These are the APIs available during code execution. * * Behavior: * - No options: Returns APIs based on client capabilities (default filtering) * - apis: ['llm', 'cache']: Returns only specified APIs (intersection with client capabilities) * - apis: []: Returns all APIs regardless of client capabilities * * @param options - Optional filtering options * @param options.apis - Specific APIs to include (e.g., ['llm', 'cache', 'approval']) */ async getRuntimeDefinitions(options) { return await this.apiOps.getRuntimeDefinitions(options); } }; var CodeGenerator = class { static { __name(this, "CodeGenerator"); } client; constructor(client) { this.client = client; } async generateCode(intent, parameters) { const types = this.client.getTypeDefinitions(); atpRuntime.log.debug("Generating code for intent", { intent, parameters, typesLength: types.length }); return "// Generated code"; } }; // src/tools.ts function createToolsFromATPClient(client)