@mondaydotcomorg/atp-client
Version:
Client SDK for Agent Tool Protocol
1,183 lines (1,175 loc) • 37.1 kB
JavaScript
import { ExecutionStatus, CallbackType, ToolOperation } from '@mondaydotcomorg/atp-protocol';
export { CallbackType } from '@mondaydotcomorg/atp-protocol';
import { log } from '@mondaydotcomorg/atp-runtime';
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) {
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 === ExecutionStatus.PAUSED && result.needsCallbacks) {
return await this.handleBatchCallbacksAndResume(result);
}
if (result.status === 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(", ")}`;
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);
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);
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`;
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);
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 CallbackType.LLM:
return this.lastExecutionConfig.clientServices.hasLLM;
case CallbackType.APPROVAL:
return this.lastExecutionConfig.clientServices.hasApproval;
case CallbackType.EMBEDDING:
return this.lastExecutionConfig.clientServices.hasEmbedding;
case 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 === ExecutionStatus.PAUSED && result.needsCallbacks) {
return await this.handleBatchCallbacksAndResume(result);
}
if (result.status === 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 === ExecutionStatus.PAUSED && result.needsCallbacks) {
return await this.handleBatchCallbacksAndResume(result);
}
if (result.status === 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 CallbackType.LLM:
return !!this.providers.llm;
case CallbackType.APPROVAL:
return !!this.providers.approval;
case CallbackType.EMBEDDING:
return !!this.providers.embedding;
case 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 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 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 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 CallbackType.TOOL:
if (payload.operation === 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}`);
}
}
};
export { APIOperations, ClientSession, ExecutionOperations, InProcessSession, ServiceProviders };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map