モデルを選択可能に変更

This commit is contained in:
nrslib 2026-01-26 19:08:22 +09:00
parent 351327a779
commit dad627ef03
12 changed files with 169 additions and 11 deletions

View File

@ -107,6 +107,8 @@ steps:
- name: step-name
agent: ~/.takt/agents/default/coder.md # Path to agent prompt
agent_name: coder # Display name (optional)
provider: codex # claude|codex (optional)
model: opus # Model name (optional)
instruction_template: |
{task}
{previous_response}
@ -131,6 +133,21 @@ steps:
| `{git_diff}` | Current git diff (uncommitted changes) |
| `{report_dir}` | Report directory name (e.g., `20250126-143052-task-summary`) |
### Model Resolution
Model is resolved in the following priority order:
1. **Workflow step `model`** - Highest priority (specified in step YAML)
2. **Custom agent `model`** - Agent-level model in `.takt/agents.yaml`
3. **Global config `model`** - Default model in `~/.takt/config.yaml`
4. **Provider default** - Falls back to provider's default (Claude: sonnet, Codex: gpt-5.2-codex)
Example `~/.takt/config.yaml`:
```yaml
provider: claude
model: opus # Default model for all steps (unless overridden)
```
## TypeScript Notes
- ESM modules with `.js` extensions in imports

View File

@ -1,12 +1,18 @@
# TAKT
**T**ask **A**gent **K**oordination **T**ool - Multi-agent orchestration system for Claude Code (Codex support planned).
🇯🇵 [日本語ドキュメント](./docs/README.ja.md)
**T**ask **A**gent **K**oordination **T**ool - Multi-agent orchestration system for Claude Code and OpenAI Codex.
> **Note**: This project is developed at my own pace. See [Disclaimer](#disclaimer) for details.
TAKT is built with TAKT (dogfooding).
## Requirements
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) must be installed and configured
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or Codex must be installed and configured
TAKT supports both Claude Code and Codex as providers; you can choose the provider during setup.
## Installation
@ -50,8 +56,20 @@ name: default
max_iterations: 10
steps:
- name: plan
agent: planner
provider: claude # Optional: claude or codex
model: opus # Claude: opus/sonnet/haiku, Codex: gpt-5.2-codex/gpt-5.1-codex
instruction_template: |
{task}
transitions:
- condition: done
next_step: implement
- name: implement
agent: coder
provider: codex
model: gpt-5.2-codex # Codex model example
instruction_template: |
{task}
transitions:
@ -62,6 +80,7 @@ steps:
- name: review
agent: architect
model: sonnet # Model alias (no provider = uses global default)
transitions:
- condition: approved
next_step: COMPLETE
@ -84,20 +103,76 @@ agents:
- name: my-reviewer
prompt_file: .takt/prompts/reviewer.md
allowed_tools: [Read, Glob, Grep]
provider: claude # Optional: claude or codex
model: opus # Claude: opus/sonnet/haiku or full name (claude-opus-4-5-20251101)
status_patterns:
approved: "\\[APPROVE\\]"
rejected: "\\[REJECT\\]"
- name: my-codex-agent
prompt_file: .takt/prompts/analyzer.md
provider: codex
model: gpt-5.2-codex # Codex: gpt-5.2-codex, gpt-5.1-codex, etc.
```
## Model Selection
### Claude Models
You can specify models using either **aliases** or **full model names**:
**Aliases** (recommended for simplicity):
- `opus` - Claude Opus 4.5 (highest reasoning capability)
- `sonnet` - Claude Sonnet 4.5 (balanced, best for most tasks)
- `haiku` - Claude Haiku 4.5 (fast and efficient)
- `opusplan` - Opus for planning, Sonnet for execution
- `default` - Recommended model for your account type
**Full model names** (recommended for production):
- `claude-opus-4-5-20251101`
- `claude-sonnet-4-5-20250929`
- `claude-haiku-4-5-20250101`
### Codex Models
Available Codex models:
- `gpt-5.2-codex` - Latest agentic coding model (default)
- `gpt-5.1-codex` - Previous generation
- `gpt-5.1-codex-max` - Optimized for long-running tasks
- `gpt-5.1-codex-mini` - Smaller, cost-effective version
- `codex-1` - Specialized model aligned with coding preferences
## Project Structure
```
~/.takt/
├── config.yaml # Global config
├── config.yaml # Global config (provider, model, workflows, etc.)
├── workflows/ # Workflow definitions
└── agents/ # Agent prompt files
```
### Global Configuration
Configure default provider and model in `~/.takt/config.yaml`:
```yaml
# ~/.takt/config.yaml
language: en
default_workflow: default
log_level: info
provider: claude # Default provider: claude or codex
model: sonnet # Default model (optional)
trusted_directories:
- /path/to/trusted/dir
```
**Model Resolution Priority:**
1. Workflow step `model` (highest priority)
2. Custom agent `model`
3. Global config `model`
4. Provider default (Claude: sonnet, Codex: gpt-5.2-codex)
## Practical Usage Guide
### Resuming Sessions with `-r`
@ -296,7 +371,6 @@ This ensures the project works correctly in a clean Node.js 20 environment.
## Documentation
- 🇯🇵 [日本語ドキュメント](./docs/README.ja.md) - Japanese documentation
- [Workflow Guide](./docs/workflows.md) - Create and customize workflows
- [Agent Guide](./docs/agents.md) - Configure custom agents
- [Changelog](./CHANGELOG.md) - Version history

View File

@ -6,6 +6,7 @@
"types": "dist/index.d.ts",
"bin": {
"takt": "./bin/takt",
"takt-dev": "./bin/takt",
"takt-cli": "./dist/cli.js"
},
"scripts": {

View File

@ -16,6 +16,11 @@ log_level: info
# Provider runtime: claude or codex
provider: claude
# Default model (optional)
# Claude: opus, sonnet, haiku, opusplan, default, or full model name
# Codex: gpt-5.2-codex, gpt-5.1-codex, etc.
# model: sonnet
# Debug settings (optional)
# debug:
# enabled: false

View File

@ -16,6 +16,11 @@ log_level: info
# プロバイダー: claude または codex
provider: claude
# デフォルトモデル (オプション)
# Claude: opus, sonnet, haiku, opusplan, default, またはフルモデル名
# Codex: gpt-5.2-codex, gpt-5.1-codex など
# model: sonnet
# デバッグ設定 (オプション)
# debug:
# enabled: false

View File

@ -54,6 +54,18 @@ function resolveProvider(cwd: string, options?: RunAgentOptions, agentConfig?: C
return 'claude';
}
function resolveModel(cwd: string, options?: RunAgentOptions, agentConfig?: CustomAgentConfig): string | undefined {
if (options?.model) return options.model;
if (agentConfig?.model) return agentConfig.model;
try {
const globalConfig = loadGlobalConfig();
if (globalConfig.model) return globalConfig.model;
} catch {
// Ignore missing global config
}
return undefined;
}
/** Get git diff for review context */
export function getGitDiff(cwd: string): string {
try {
@ -91,7 +103,7 @@ export async function runCustomAgent(
cwd: options.cwd,
sessionId: options.sessionId,
allowedTools,
model: options.model || agentConfig.model,
model: resolveModel(options.cwd, options, agentConfig),
onStream: options.onStream,
onPermissionRequest: options.onPermissionRequest,
onAskUserQuestion: options.onAskUserQuestion,
@ -106,7 +118,7 @@ export async function runCustomAgent(
cwd: options.cwd,
sessionId: options.sessionId,
allowedTools,
model: options.model || agentConfig.model,
model: resolveModel(options.cwd, options, agentConfig),
onStream: options.onStream,
onPermissionRequest: options.onPermissionRequest,
onAskUserQuestion: options.onAskUserQuestion,
@ -119,11 +131,12 @@ export async function runCustomAgent(
const systemPrompt = loadAgentPrompt(agentConfig);
const tools = allowedTools;
const provider = resolveProvider(options.cwd, options, agentConfig);
const model = resolveModel(options.cwd, options, agentConfig);
if (provider === 'codex') {
const callOptions: CodexCallOptions = {
cwd: options.cwd,
sessionId: options.sessionId,
model: options.model || agentConfig.model,
model,
statusPatterns: agentConfig.statusPatterns,
onStream: options.onStream,
};
@ -134,7 +147,7 @@ export async function runCustomAgent(
cwd: options.cwd,
sessionId: options.sessionId,
allowedTools: tools,
model: options.model || agentConfig.model,
model,
statusPatterns: agentConfig.statusPatterns,
onStream: options.onStream,
onPermissionRequest: options.onPermissionRequest,
@ -196,12 +209,13 @@ export async function runAgent(
const systemPrompt = loadAgentPromptFromPath(options.agentPath);
const tools = options.allowedTools;
const provider = resolveProvider(options.cwd, options);
const model = resolveModel(options.cwd, options);
if (provider === 'codex') {
const callOptions: CodexCallOptions = {
cwd: options.cwd,
sessionId: options.sessionId,
model: options.model,
model,
systemPrompt,
onStream: options.onStream,
};
@ -212,7 +226,7 @@ export async function runAgent(
cwd: options.cwd,
sessionId: options.sessionId,
allowedTools: tools,
model: options.model,
model,
systemPrompt,
onStream: options.onStream,
onPermissionRequest: options.onPermissionRequest,

View File

@ -367,6 +367,7 @@ export async function callCodex(
const { events } = await thread.runStreamed(fullPrompt);
let content = '';
const contentOffsets = new Map<string, number>();
let success = true;
let failureMessage = '';
const startedItems = new Set<string>();
@ -406,6 +407,20 @@ export async function callCodex(
if (event.type === 'item.updated') {
const item = event.item as CodexItem | undefined;
if (item) {
if (item.type === 'agent_message' && typeof item.text === 'string') {
const itemId = item.id;
const text = item.text;
if (itemId) {
const prev = contentOffsets.get(itemId) ?? 0;
if (text.length > prev) {
if (prev === 0 && content.length > 0) {
content += '\n';
}
content += text.slice(prev);
contentOffsets.set(itemId, text.length);
}
}
}
emitCodexItemUpdate(item, options.onStream, startedItems, outputOffsets, textOffsets, thinkingOffsets);
}
continue;
@ -415,7 +430,23 @@ export async function callCodex(
const item = event.item as CodexItem | undefined;
if (item) {
if (item.type === 'agent_message' && typeof item.text === 'string') {
content = item.text;
const itemId = item.id;
const text = item.text;
if (itemId) {
const prev = contentOffsets.get(itemId) ?? 0;
if (text.length > prev) {
if (prev === 0 && content.length > 0) {
content += '\n';
}
content += text.slice(prev);
contentOffsets.set(itemId, text.length);
}
} else if (text) {
if (content.length > 0) {
content += '\n';
}
content += text;
}
}
emitCodexItemCompleted(
item,

View File

@ -30,6 +30,7 @@ export function loadGlobalConfig(): GlobalConfig {
defaultWorkflow: parsed.default_workflow,
logLevel: parsed.log_level,
provider: parsed.provider,
model: parsed.model,
debug: parsed.debug ? {
enabled: parsed.debug.enabled,
logFile: parsed.debug.log_file,
@ -47,6 +48,9 @@ export function saveGlobalConfig(config: GlobalConfig): void {
log_level: config.logLevel,
provider: config.provider,
};
if (config.model) {
raw.model = config.model;
}
if (config.debug) {
raw.debug = {
enabled: config.debug.enabled,

View File

@ -68,6 +68,7 @@ function normalizeWorkflowConfig(raw: unknown, workflowDir: string): WorkflowCon
agentPath: resolveAgentPathForWorkflow(step.agent, workflowDir),
allowedTools: step.allowed_tools,
provider: step.provider,
model: step.model,
instructionTemplate: step.instruction_template || step.instruction || '{task}',
transitions: step.transitions.map((t) => ({
condition: t.condition,

View File

@ -61,6 +61,7 @@ export const WorkflowStepRawSchema = z.object({
agent_name: z.string().optional(),
allowed_tools: z.array(z.string()).optional(),
provider: z.enum(['claude', 'codex']).optional(),
model: z.string().optional(),
instruction: z.string().optional(),
instruction_template: z.string().optional(),
pass_previous_response: z.boolean().optional().default(true),
@ -115,6 +116,7 @@ export const GlobalConfigSchema = z.object({
default_workflow: z.string().optional().default('default'),
log_level: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),
provider: z.enum(['claude', 'codex']).optional().default('claude'),
model: z.string().optional(),
debug: DebugConfigSchema.optional(),
});

View File

@ -66,6 +66,8 @@ export interface WorkflowStep {
agentPath?: string;
/** Provider override for this step */
provider?: 'claude' | 'codex';
/** Model override for this step */
model?: string;
instructionTemplate: string;
transitions: WorkflowTransition[];
passPreviousResponse: boolean;
@ -143,6 +145,7 @@ export interface GlobalConfig {
defaultWorkflow: string;
logLevel: 'debug' | 'info' | 'warn' | 'error';
provider?: 'claude' | 'codex';
model?: string;
debug?: DebugConfig;
}

View File

@ -149,6 +149,7 @@ export class WorkflowEngine extends EventEmitter {
agentPath: step.agentPath,
allowedTools: step.allowedTools,
provider: step.provider,
model: step.model,
onStream: this.options.onStream,
onPermissionRequest: this.options.onPermissionRequest,
onAskUserQuestion: this.options.onAskUserQuestion,