mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-25 08:48:08 +02:00
Initial open-source release
This commit is contained in:
commit
1a42152e6f
1199 changed files with 257054 additions and 0 deletions
174
packages/context/src/tools/base-tool.ts
Normal file
174
packages/context/src/tools/base-tool.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { tool } from 'ai';
|
||||
import { z, type ZodType } from 'zod';
|
||||
import { noopLogger, type KloLogger } from '../core/index.js';
|
||||
import type { IngestToolMetadata, ToolSession } from './tool-session.js';
|
||||
|
||||
export interface ToolOutput<T = unknown> {
|
||||
markdown: string;
|
||||
structured: T;
|
||||
}
|
||||
|
||||
export interface ToolTimingTrackerPort {
|
||||
recordToolExecutionStart(messageId: string, toolName: string, toolCallId: string): void;
|
||||
recordToolExecutionEnd(messageId: string, toolName: string, toolCallId: string, state: string): void;
|
||||
}
|
||||
|
||||
export interface ToolProgressRelayPort {
|
||||
emit(event: unknown): void;
|
||||
}
|
||||
|
||||
type ChatSource =
|
||||
| 'RESEARCH'
|
||||
| 'DASHBOARD'
|
||||
| 'WIDGET_CONFIG'
|
||||
| 'EVALUATION'
|
||||
| 'METRIC_WORKSHOP'
|
||||
| 'INPUT_CONFIG'
|
||||
| 'SCHEDULED_RESEARCH'
|
||||
| 'DASHBOARD_GENERATION';
|
||||
|
||||
export interface ToolContext {
|
||||
sourceId: string;
|
||||
messageId: string;
|
||||
userId: string;
|
||||
userRoles?: string[];
|
||||
authToken?: string;
|
||||
currentUserMessage?: string;
|
||||
toolCallId?: string;
|
||||
toolCallHistory?: string[];
|
||||
timingTracker?: ToolTimingTrackerPort;
|
||||
source?: ChatSource;
|
||||
dashboardId?: string;
|
||||
methodologyEntries?: MethodologyEntry[];
|
||||
progressRelay?: ToolProgressRelayPort;
|
||||
connectionId?: string;
|
||||
ingest?: IngestToolMetadata;
|
||||
/**
|
||||
* Per-session state (ingest WU, memory-agent post-turn). When present, SL/wiki
|
||||
* tools use session-scoped services and emit touched-set entries instead of
|
||||
* writing to shared indexes immediately. Non-session callers leave this unset.
|
||||
*/
|
||||
session?: ToolSession;
|
||||
currentDefinition?: {
|
||||
sql: string;
|
||||
measures: unknown[];
|
||||
dimensions: unknown[];
|
||||
parameters: unknown[];
|
||||
segments: unknown[];
|
||||
name?: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MethodologyEntry {
|
||||
key: string;
|
||||
toolName: string;
|
||||
label: string;
|
||||
args: Record<string, unknown>;
|
||||
result?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* SECURITY: All tools require authentication. userId must always be provided in ToolContext.
|
||||
*/
|
||||
export abstract class BaseTool<TInput extends ZodType = ZodType> {
|
||||
protected readonly logger: KloLogger;
|
||||
|
||||
abstract readonly name: string;
|
||||
|
||||
constructor(logger: KloLogger = noopLogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
abstract get description(): string;
|
||||
|
||||
abstract get inputSchema(): TInput;
|
||||
|
||||
abstract call(input: z.infer<TInput>, context: ToolContext): Promise<any>;
|
||||
|
||||
getParametersSchema(): {
|
||||
type: 'object';
|
||||
properties: Record<string, any>;
|
||||
required?: string[];
|
||||
} {
|
||||
const jsonSchema = z.toJSONSchema(this.inputSchema, {
|
||||
target: 'draft-7',
|
||||
});
|
||||
|
||||
return jsonSchema as any;
|
||||
}
|
||||
|
||||
toAnthropicFormat(): {
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: {
|
||||
type: 'object';
|
||||
properties: Record<string, any>;
|
||||
required?: string[];
|
||||
};
|
||||
} {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
input_schema: this.getParametersSchema(),
|
||||
};
|
||||
}
|
||||
|
||||
toAiSdkTool(context: ToolContext): any {
|
||||
const toolName = this.name;
|
||||
const logger = this.logger;
|
||||
|
||||
return tool({
|
||||
description: this.description,
|
||||
inputSchema: this.inputSchema,
|
||||
execute: async (params, { toolCallId }) => {
|
||||
// Create context copy with current toolCallId (safe for parallel execution)
|
||||
const callContext = { ...context, toolCallId };
|
||||
|
||||
// Record tool execution start (input generation has already been tracked via onChunk)
|
||||
if (callContext.timingTracker && toolCallId) {
|
||||
callContext.timingTracker.recordToolExecutionStart(callContext.messageId, toolName, toolCallId);
|
||||
}
|
||||
|
||||
let state = 'completed';
|
||||
try {
|
||||
if (!callContext.userId) {
|
||||
throw new Error('Authentication required: userId must be provided in ToolContext');
|
||||
}
|
||||
const parsedInput = this.parseInput(params as Record<string, any>);
|
||||
const result = await this.call(parsedInput, callContext);
|
||||
return result;
|
||||
} catch (error) {
|
||||
state = 'error';
|
||||
this.logger.error(
|
||||
`Tool ${this.name} execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
// Record tool execution end
|
||||
if (callContext.timingTracker && toolCallId) {
|
||||
callContext.timingTracker.recordToolExecutionEnd(callContext.messageId, toolName, toolCallId, state);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Send only markdown to LLM - frontend still receives full { markdown, structured } via stream
|
||||
toModelOutput: ({ output }) => {
|
||||
if (output && typeof output === 'object' && 'markdown' in output) {
|
||||
return { type: 'content', value: [{ type: 'text', text: output.markdown as string }] };
|
||||
}
|
||||
if (typeof output !== 'string') {
|
||||
logger.warn(`Tool ${toolName} returned unexpected output type: ${typeof output}. Coercing to string.`);
|
||||
}
|
||||
return { type: 'content', value: [{ type: 'text', text: String(output) }] };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
parseInput(input: Record<string, any>): z.infer<TInput> {
|
||||
return this.inputSchema.parse(input);
|
||||
}
|
||||
|
||||
protected getCurrentUserQuery(context: ToolContext): string | null {
|
||||
return context.currentUserMessage ?? null;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue