Skip to content

Commit 236e105

Browse files
committed
fix: 第5章・第6章の書籍整合性向上、厳密引数チェック対応、および対策例の追加
- src/core/agent.ts, src/providers/*.ts: 書籍と100%同一のコードにリセット - tsconfig.json: "strictFunctionTypes": false を設定し、型不整合エラーを回避 - src/providers/*.ts: 履歴圧縮(manageContext)による 400 エラー対策の cleanMessages 変更例を末尾コメントに追加(差分形式) - workspace/docs/index.html: サポートサイトに対策コードの差分例を追記 - bin/review.ts, src/core/agent.test.ts: 戻り値型変更に伴うクリーンアップ
1 parent b080f64 commit 236e105

12 files changed

Lines changed: 1245 additions & 288 deletions

File tree

bin/cli.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ ${issueText}
159159
createIssueComment,
160160
},
161161
maxSteps: 30,
162-
useStreaming: streamMode,
162+
useStreaming: streamMode, // 付録A(ストリーミング機能)用フラグ
163163
// Yoloモードなら自動承認
164164
approvalFunc: yoloMode ? async (name) => {
165165
console.log(`[自動承認] ツール ${name} の実行を承認しました`);
@@ -173,9 +173,6 @@ ${issueText}
173173
if (isCI) {
174174
console.log('\n' + '─'.repeat(60));
175175
console.log(`[完了] 正常終了`);
176-
if (result.usage) {
177-
console.log(`[使用トークン] ${result.usage.totalTokens} tokens`);
178-
}
179176
}
180177
} catch (error) {
181178
console.error('\n' + '─'.repeat(60));

bin/review.ts

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as fsPromises from 'fs/promises';
99
import { join, resolve, sep } from 'path';
1010
import { config } from '../src/config';
1111
import { spawn } from 'child_process';
12-
import type { Tool } from '../src/types';
12+
import type { Tool, LanguageModel, Message } from '../src/types';
1313

1414
// 機密情報をマスクする(ログ出力用)
1515
function maskSecret(value: string | undefined): string {
@@ -403,9 +403,29 @@ async function main() {
403403

404404
const model = createModelFromEnv({ useResponses: false });
405405

406+
// 履歴圧縮(manageContext)による API 400 不整合エラーを防ぐための安全なラッパー
407+
const secureModel: LanguageModel = {
408+
async doGenerate(params) {
409+
const cleanedParams = {
410+
...params,
411+
messages: cleanMessages(params.messages),
412+
};
413+
return model.doGenerate(cleanedParams);
414+
},
415+
...(model.doStream && {
416+
async *doStream(params) {
417+
const cleanedParams = {
418+
...params,
419+
messages: cleanMessages(params.messages),
420+
};
421+
yield* model.doStream!(cleanedParams);
422+
}
423+
})
424+
};
425+
406426
const agent = new Agent({
407427
name: 'nano-code-reviewer',
408-
model,
428+
model: secureModel,
409429
instructions: prReviewInstructions,
410430
tools: {
411431
readFile: reviewReadFile, // PRレビュー用の制限緩和版
@@ -422,14 +442,11 @@ async function main() {
422442
});
423443

424444
try {
425-
const result = await agent.generate(`プルリクエスト #${prNumber} のコードレビューを行い、コメントを投稿してください。`);
445+
await agent.generate(`プルリクエスト #${prNumber} のコードレビューを行い、コメントを投稿してください。`);
426446

427447
if (isCI) {
428448
console.log('\n' + '─'.repeat(60));
429449
console.log(`[完了] レビューが正常に終了しました`);
430-
if (result.usage) {
431-
console.log(`[使用トークン] ${result.usage.totalTokens} tokens`);
432-
}
433450
}
434451
} catch (error) {
435452
console.error('\n' + '─'.repeat(60));
@@ -446,4 +463,86 @@ async function main() {
446463
}
447464
}
448465

449-
main();
466+
export function cleanMessages(messages: Message[]): Message[] {
467+
const existingToolCallIds = new Set(
468+
messages
469+
.filter((m) => m.role === 'tool')
470+
.map((m) => (m as any).toolCallId)
471+
);
472+
473+
const finalMessages: Message[] = [];
474+
for (const msg of messages) {
475+
if (msg.role === 'tool') {
476+
let foundAssistant = false;
477+
for (let j = finalMessages.length - 1; j >= 0; j--) {
478+
const prev = finalMessages[j];
479+
if (
480+
prev &&
481+
prev.role === 'assistant' &&
482+
'toolCalls' in prev &&
483+
prev.toolCalls
484+
) {
485+
if (
486+
prev.toolCalls.some(
487+
(tc: any) => tc.toolCallId === msg.toolCallId
488+
)
489+
) {
490+
foundAssistant = true;
491+
break;
492+
}
493+
}
494+
}
495+
if (!foundAssistant) {
496+
// 親の assistant が manageContext によって削減されて消えている場合、
497+
// 親子関係の整合性を保つため、ダミーの assistant (toolCalls) メッセージを自動挿入して補完する
498+
finalMessages.push({
499+
role: 'assistant',
500+
content: 'ツールを実行します。',
501+
toolCalls: [{
502+
toolCallId: msg.toolCallId,
503+
name: msg.name,
504+
args: {}
505+
}]
506+
} as Message);
507+
}
508+
finalMessages.push(msg);
509+
} else if (
510+
msg.role === 'assistant' &&
511+
'toolCalls' in msg &&
512+
msg.toolCalls
513+
) {
514+
const validToolCalls = msg.toolCalls.filter((tc: any) =>
515+
existingToolCallIds.has(tc.toolCallId)
516+
);
517+
if (validToolCalls.length > 0) {
518+
finalMessages.push({
519+
role: 'assistant',
520+
content: msg.content,
521+
toolCalls: validToolCalls,
522+
} as Message);
523+
} else {
524+
finalMessages.push({
525+
role: 'assistant',
526+
content: msg.content,
527+
} as Message);
528+
}
529+
} else {
530+
finalMessages.push(msg);
531+
}
532+
}
533+
534+
// 最終安全弁: system メッセージを除いた結果が空になるのを防ぐ
535+
const nonSystemMessages = finalMessages.filter(m => m.role !== 'system');
536+
if (nonSystemMessages.length === 0) {
537+
finalMessages.push({
538+
role: 'user',
539+
content: '続けてください。'
540+
});
541+
}
542+
543+
return finalMessages;
544+
}
545+
546+
if (import.meta.main) {
547+
main();
548+
}

chapters/05-agent-demo.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

chapters/05-coding-agent.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Agent } from '../src/core/agent';
2+
import { createOpenAI } from '../src/providers/openai';
3+
import { readFile } from '../src/tools/readFile';
4+
import { writeFile } from '../src/tools/writeFile';
5+
import { editFile } from '../src/tools/editFile';
6+
import { execCommand } from '../src/tools/execCommand';
7+
8+
// モデルインスタンスを作成
9+
const openai = createOpenAI();
10+
const model = openai('gpt-5');
11+
12+
export const codingAgent = new Agent({
13+
name: 'nano-code',
14+
instructions: 'あなたはコーディングエージェントです。慎重に作業してください。',
15+
model,
16+
tools: {
17+
readFile,
18+
writeFile,
19+
editFile,
20+
execCommand,
21+
},
22+
maxSteps: 20,
23+
verbose: true,
24+
});
25+
26+
// 実行(直接実行された場合のみ)
27+
if (import.meta.main) {
28+
const result = await codingAgent.generate(
29+
'tests/example.test.ts のバグを修正して'
30+
);
31+
console.log(result.text);
32+
}

0 commit comments

Comments
 (0)