feat: add tool framework foundation (types, registry, executor, shell tool, model types, SOUL.md)
- Task 0: SOUL.md + loadSystemPrompt() in daemon - Task 1: Tool type definitions (Tool, ToolCall, ToolResult, etc.) - Task 2: ToolRegistry with Anthropic/OpenAI serialization - Task 3: ToolExecutor with hooks, timeout, truncation - Task 4: shell.exec builtin tool - Task 8: Model types updated for tool use (ToolDefinition, ModelToolCall, etc.) - Task 15: Model index exports for tool types
This commit is contained in:
+15
-1
@@ -3,4 +3,18 @@ export { OpenAIClient, type OpenAIClientConfig } from './openai.js';
|
||||
export { OllamaClient, type OllamaClientConfig } from './local/index.js';
|
||||
export { LlamaCppClient, type LlamaCppClientConfig } from './local/index.js';
|
||||
export { ModelRouter, type ModelRouterConfig, type ModelTier } from './router.js';
|
||||
export type { Message, ChatRequest, ChatResponse, ModelClient } from './types.js';
|
||||
export type {
|
||||
Message,
|
||||
ChatRequest,
|
||||
ChatResponse,
|
||||
ChatStreamEvent,
|
||||
TokenUsage,
|
||||
ModelClient,
|
||||
StreamingModelClient,
|
||||
ToolDefinition,
|
||||
ModelToolCall,
|
||||
ContentBlock,
|
||||
ToolResultEntry,
|
||||
ToolMessage,
|
||||
ConversationMessage,
|
||||
} from './types.js';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { ChatRequest, ChatResponse, ToolMessage, ContentBlock } from './types.js';
|
||||
|
||||
describe('Model types with tool support', () => {
|
||||
it('ChatRequest accepts tools array', () => {
|
||||
const req: ChatRequest = {
|
||||
messages: [{ role: 'user', content: 'hi' }],
|
||||
tools: [{
|
||||
name: 'test',
|
||||
description: 'test tool',
|
||||
input_schema: { type: 'object', properties: {} },
|
||||
}],
|
||||
};
|
||||
expect(req.tools).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('ChatResponse has optional toolCalls', () => {
|
||||
const resp: ChatResponse = {
|
||||
content: '',
|
||||
stopReason: 'tool_use',
|
||||
usage: { inputTokens: 0, outputTokens: 0 },
|
||||
toolCalls: [{ id: 'call_1', name: 'test', args: {} }],
|
||||
};
|
||||
expect(resp.toolCalls).toHaveLength(1);
|
||||
expect(resp.stopReason).toBe('tool_use');
|
||||
});
|
||||
|
||||
it('ChatResponse works without toolCalls (backward compatible)', () => {
|
||||
const resp: ChatResponse = {
|
||||
content: 'hello',
|
||||
stopReason: 'end_turn',
|
||||
usage: { inputTokens: 10, outputTokens: 5 },
|
||||
};
|
||||
expect(resp.toolCalls).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ToolMessage represents tool results in conversation', () => {
|
||||
const msg: ToolMessage = {
|
||||
role: 'tool_result',
|
||||
toolResults: [{ tool_use_id: 'call_1', content: 'result', is_error: false }],
|
||||
};
|
||||
expect(msg.role).toBe('tool_result');
|
||||
expect(msg.toolResults).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('ContentBlock can be text or tool_use', () => {
|
||||
const text: ContentBlock = { type: 'text', text: 'hello' };
|
||||
const tool: ContentBlock = { type: 'tool_use', id: 'c1', name: 'test', input: {} };
|
||||
expect(text.type).toBe('text');
|
||||
expect(tool.type).toBe('tool_use');
|
||||
});
|
||||
});
|
||||
+44
-2
@@ -4,16 +4,57 @@ export interface Message {
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
// Tool definition passed to model API
|
||||
export interface ToolDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: {
|
||||
type: 'object';
|
||||
properties: Record<string, unknown>;
|
||||
required?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
// Individual tool call returned by model
|
||||
export interface ModelToolCall {
|
||||
id: string;
|
||||
name: string;
|
||||
args: unknown;
|
||||
}
|
||||
|
||||
// Content blocks for multi-content responses
|
||||
export type ContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'tool_use'; id: string; name: string; input: unknown };
|
||||
|
||||
// Tool result fed back into conversation
|
||||
export interface ToolResultEntry {
|
||||
tool_use_id: string;
|
||||
content: string;
|
||||
is_error?: boolean;
|
||||
}
|
||||
|
||||
// Message type for tool results (distinct from user/assistant)
|
||||
export interface ToolMessage {
|
||||
role: 'tool_result';
|
||||
toolResults: ToolResultEntry[];
|
||||
}
|
||||
|
||||
// Union type for all messages in a conversation
|
||||
export type ConversationMessage = Message | ToolMessage;
|
||||
|
||||
export interface ChatRequest {
|
||||
messages: Message[];
|
||||
system?: string;
|
||||
maxTokens?: number;
|
||||
tools?: ToolDefinition[];
|
||||
}
|
||||
|
||||
export interface ChatResponse {
|
||||
content: string;
|
||||
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | string;
|
||||
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use' | string;
|
||||
usage: TokenUsage;
|
||||
toolCalls?: ModelToolCall[];
|
||||
}
|
||||
|
||||
export interface TokenUsage {
|
||||
@@ -22,10 +63,11 @@ export interface TokenUsage {
|
||||
}
|
||||
|
||||
export interface ChatStreamEvent {
|
||||
type: 'content' | 'done' | 'error';
|
||||
type: 'content' | 'done' | 'error' | 'tool_use';
|
||||
content?: string;
|
||||
usage?: TokenUsage;
|
||||
error?: Error;
|
||||
toolCall?: ModelToolCall;
|
||||
}
|
||||
|
||||
export interface StreamingModelClient {
|
||||
|
||||
Reference in New Issue
Block a user