@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
360 lines (359 loc) • 16.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.HostProvider = void 0;
const path = __importStar(require("path"));
const prompts_1 = require("../../tools/prompts");
const model_config_1 = require("../model-config");
const provider_debug_utils_1 = require("./provider-debug-utils");
const ai_tracing_1 = require("../tracing/ai-tracing");
const tool_utils_1 = require("./tool-utils");
const investigation_1 = require("../constants/investigation");
class HostProvider {
static samplingHandler;
debugMode;
constructor() {
this.debugMode = process.env.DEBUG_DOT_AI === 'true';
}
setSamplingHandler(handler) {
HostProvider.samplingHandler = handler;
}
isInitialized() {
return !!HostProvider.samplingHandler;
}
getDefaultModel() {
return model_config_1.CURRENT_MODELS.host;
}
getProviderType() {
return model_config_1.CURRENT_MODELS.host;
}
getModelName() {
return model_config_1.CURRENT_MODELS.host;
}
logDebugIfEnabled(operation, prompt, response) {
if (!this.debugMode)
return null;
const debugId = (0, provider_debug_utils_1.generateDebugId)(operation);
(0, provider_debug_utils_1.debugLogInteraction)(debugId, prompt, response, operation, this.getProviderType(), this.getModelName(), this.debugMode);
return {
promptFile: `${debugId}_prompt.md`,
responseFile: `${debugId}_response.md`,
};
}
async sendMessage(message, operation = 'generic', evaluationContext) {
if (!HostProvider.samplingHandler) {
throw new Error('Host provider is not connected to MCP server');
}
return await (0, ai_tracing_1.withAITracing)({
provider: this.getProviderType(),
model: this.getModelName(),
operation: 'chat',
}, async () => {
const startTime = Date.now();
const messages = [
{ role: 'user', content: { type: 'text', text: message } },
];
try {
const result = await HostProvider.samplingHandler(messages, undefined, {
operation,
evaluationContext,
});
let content = '';
if (typeof result.content === 'object' &&
result.content.type === 'text') {
content = result.content.text;
}
else if (typeof result.content === 'string') {
content = result.content;
}
else {
content = JSON.stringify(result.content);
}
const response = {
content,
usage: {
input_tokens: 0,
output_tokens: 0,
},
};
const durationMs = Date.now() - startTime;
// Debug log the interaction if enabled
if (this.debugMode) {
this.logDebugIfEnabled(operation, message, response);
const evaluationMetrics = {
operation,
sdk: this.getProviderType(),
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
durationMs,
iterationCount: 1,
toolCallCount: 0,
status: 'completed',
completionReason: 'stop',
modelVersion: this.getModelName(),
test_scenario: operation,
ai_response_summary: response.content,
user_intent: evaluationContext?.user_intent || '',
interaction_id: evaluationContext?.interaction_id || '',
};
(0, provider_debug_utils_1.logEvaluationDataset)(evaluationMetrics, this.debugMode);
}
return response;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (this.debugMode) {
const debugId = (0, provider_debug_utils_1.generateDebugId)(operation);
(0, provider_debug_utils_1.debugLogPromptOnly)(debugId, message, operation, this.getProviderType(), this.getModelName(), this.debugMode);
if (evaluationContext) {
const failureMetrics = {
operation,
user_intent: evaluationContext.user_intent || '',
ai_response_summary: `Error: ${errorMessage}`,
durationMs: Date.now() - startTime,
inputTokens: 0,
outputTokens: 0,
iterationCount: 0,
toolCallCount: 0,
status: 'failed',
completionReason: 'error',
sdk: this.getProviderType(),
modelVersion: this.getModelName(),
test_scenario: operation,
interaction_id: evaluationContext.interaction_id ||
(0, provider_debug_utils_1.generateDebugId)(operation),
failure_analysis: {
failure_type: 'error',
failure_reason: `Host API error: ${errorMessage}`,
time_to_failure: Date.now() - startTime,
},
};
(0, provider_debug_utils_1.logEvaluationDataset)(failureMetrics, this.debugMode);
}
}
throw new Error(`Host sampling error: ${errorMessage}`, {
cause: error,
});
}
}, (response) => ({
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
}));
}
/**
* Execute a tool loop with the host model
*
* The tool loop relies on a specific JSON format embedded in markdown code blocks:
* ```json
* {
* "tool": "toolName",
* "arguments": { ... }
* }
* ```
*/
async toolLoop(config) {
if (!HostProvider.samplingHandler) {
throw new Error('Host provider is not connected to MCP server');
}
return await (0, ai_tracing_1.withAITracing)({
provider: this.getProviderType(),
model: this.getModelName(),
operation: 'tool_loop',
}, async () => {
const maxIterations = config.maxIterations || 20;
const messages = [
{ role: 'user', content: { type: 'text', text: config.userMessage } },
];
// Construct system prompt with tool definitions
const promptPath = path.join(__dirname, '..', '..', '..', 'prompts', 'host-tools.md');
const promptTemplate = (0, prompts_1.loadPromptFile)(promptPath).content;
const toolDefinitions = (0, tool_utils_1.formatToolDefinitions)(config.tools);
const systemPrompt = config.systemPrompt +
'\n\n' +
promptTemplate.replace('{{TOOL_DEFINITIONS}}', toolDefinitions);
const toolCallsExecuted = [];
let iterations = 0;
while (iterations < maxIterations) {
iterations++;
try {
const result = await HostProvider.samplingHandler(messages, systemPrompt, {
operation: config.operation,
evaluationContext: config.evaluationContext,
interaction_id: config.interaction_id,
});
let content = '';
if (typeof result.content === 'object' &&
result.content.type === 'text') {
content = result.content.text;
}
else if (typeof result.content === 'string') {
content = result.content;
}
else {
content = JSON.stringify(result.content);
}
// Add assistant response to history
messages.push({
role: 'assistant',
content: { type: 'text', text: content },
});
// Check for tool calls
const toolCalls = (0, tool_utils_1.extractToolCalls)(content);
if (toolCalls.length > 0) {
for (const toolCall of toolCalls) {
try {
const toolName = toolCall.tool;
const toolArgs = toolCall.arguments || {};
// Validate tool exists
const toolExists = config.tools.some(t => t.name === toolName);
if (!toolExists) {
messages.push({
role: 'user',
content: {
type: 'text',
text: `Unknown tool '${toolName}'. Available tools: ${config.tools.map(t => t.name).join(', ')}`,
},
});
continue;
}
// Execute tool
const toolOutput = await config.toolExecutor(toolName, toolArgs);
toolCallsExecuted.push({
tool: toolName,
input: toolArgs,
output: toolOutput,
});
// Add tool result to history
messages.push({
role: 'user',
content: {
type: 'text',
text: (0, tool_utils_1.formatToolOutput)(toolName, toolOutput),
},
});
}
catch (executionError) {
messages.push({
role: 'user',
content: {
type: 'text',
text: `Error executing tool '${toolCall.tool}': ${executionError instanceof Error ? executionError.message : String(executionError)}`,
},
});
}
}
if (config.onIteration) {
try {
config.onIteration(iterations, toolCallsExecuted);
}
catch {
// Ignore errors in callback
}
}
}
else {
// No tool call, assume final response
return {
finalMessage: content,
iterations,
toolCallsExecuted,
totalTokens: { input: 0, output: 0 },
};
}
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Host sampling error in tool loop: ${message}`, {
cause: error,
});
}
}
// Max iterations reached - make one final wrap-up call WITHOUT tools
// to force the AI to summarize findings rather than continue investigating
const wrapUpMessage = investigation_1.INVESTIGATION_MESSAGES.WRAP_UP;
messages.push({
role: 'user',
content: { type: 'text', text: wrapUpMessage },
});
try {
// Make final call WITHOUT tools in system prompt - use base system prompt only
const wrapUpResult = await HostProvider.samplingHandler(messages, config.systemPrompt, // Original system prompt without tool definitions
{
operation: config.operation,
evaluationContext: config.evaluationContext,
interaction_id: config.interaction_id,
});
let wrapUpContent = '';
if (typeof wrapUpResult.content === 'object' &&
wrapUpResult.content.type === 'text') {
wrapUpContent = wrapUpResult.content.text;
}
else if (typeof wrapUpResult.content === 'string') {
wrapUpContent = wrapUpResult.content;
}
else {
wrapUpContent = JSON.stringify(wrapUpResult.content);
}
return {
finalMessage: wrapUpContent,
iterations: iterations + 1, // Include wrap-up iteration
toolCallsExecuted,
totalTokens: { input: 0, output: 0 },
status: 'timeout',
completionReason: 'max_iterations',
};
}
catch {
// If wrap-up call fails, fall back to last message
const lastMessage = messages[messages.length - 2]; // -2 because we added wrap-up message
const lastContent = typeof lastMessage.content === 'string'
? lastMessage.content
: (lastMessage.content?.text ?? '');
return {
finalMessage: lastContent,
iterations,
toolCallsExecuted,
totalTokens: { input: 0, output: 0 },
status: 'timeout',
completionReason: 'max_iterations',
};
}
}, (result) => ({
inputTokens: result.totalTokens.input,
outputTokens: result.totalTokens.output,
}));
}
}
exports.HostProvider = HostProvider;