补齐 Anthropic 原生工具与三协议流式工具调用 (#112)
Project CI / Repository checks (push) Successful in 46s
Project CI / Backend tests (push) Successful in 2m55s
Project CI / Native shell tests (push) Successful in 9m53s
Project CI / Frontend tests (push) Successful in 2m28s

Anthropic 请求发送 tools 与对象形态 tool_choice 并解析 tool_use block,解除本地对 function tools 的拦截
Chat / Responses / Anthropic 三种协议的流式工具增量按槽位聚合,收尾校验参数为完整 JSON,截断流不返回半截参数
解除 App 侧 Anthropic 降级为文本 JSON 协议的两处守卫与对应提示词分支
新增依赖真实凭据的流式工具验收用例,默认 ignore

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/112
Co-authored-by: 孔令弘 <ink29535@proton.me>
Co-committed-by: 孔令弘 <ink29535@proton.me>
This commit was merged in pull request #112.
This commit is contained in:
2026-07-28 13:33:49 +08:00
committed by 段舒康
parent 26ef00f7b9
commit d29240b954
18 changed files with 3213 additions and 350 deletions
+46 -13
View File
@@ -26,14 +26,18 @@ const aiGameCreatorShellAppSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/App.tsx',
'utf8',
);
const aiGameCreatorShellModelSource = fs.readFileSync(
const aiGameCreatorShellAppModelSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/features/app-shell/model.ts',
'utf8',
);
const aiGameCreatorShellChatPaneSource = fs.readFileSync(
const aiGameCreatorShellProjectWorkspaceChatPaneSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx',
'utf8',
);
const aiGameCreatorShellDeveloperProjectPanelsSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/features/project-workspace/DeveloperProjectPanels.tsx',
'utf8',
);
const aiGameCreatorProjectDevelopmentSource = fs.readFileSync(
'apps/ai-game-creator-shell/src/view/project-development/index.tsx',
'utf8',
@@ -2349,27 +2353,56 @@ function assertAiGameCreatorShellUserDevBoundary() {
'if (!import.meta.env.DEV)',
"return params.has('dev') || window.location.hash === '#dev';",
]) {
if (!aiGameCreatorShellModelSource.includes(snippet)) {
if (!aiGameCreatorShellAppModelSource.includes(snippet)) {
throw new Error(
`AI game creator developer-mode boundary drifted: missing ${snippet}`,
`AI game creator developer mode boundary drifted: missing ${snippet}`,
);
}
}
for (const snippet of ['{devMode ? (', 'className="developer-pane"']) {
for (const snippet of [
'projectSupervisorOnly ? false : isDeveloperMode()',
"{devMode ? (",
'className="developer-pane"',
]) {
if (!aiGameCreatorShellAppSource.includes(snippet)) {
throw new Error(
`AI game creator user/dev UI boundary drifted: missing ${snippet}`,
);
throw new Error(`AI game creator user/dev UI boundary drifted: missing ${snippet}`);
}
}
if (!aiGameCreatorShellChatPaneSource.includes('className="chat-pane"')) {
throw new Error('AI game creator chat pane boundary drifted');
if (
!aiGameCreatorShellAppSource.includes('<ProjectWorkspaceChatPane') ||
!aiGameCreatorShellProjectWorkspaceChatPaneSource.includes(
'className="chat-pane"',
)
) {
throw new Error('AI game creator user chat pane boundary drifted');
}
const previewFrameIndexes = [
...aiGameCreatorShellAppSource.matchAll(/<iframe\b/g),
const developerProjectPanelIndexes = [
...aiGameCreatorShellAppSource.matchAll(/<DeveloperProjectPanels\b/g),
].map((match) => match.index ?? -1);
if (previewFrameIndexes.length !== 0) {
const previewFrameCount = [
...aiGameCreatorShellDeveloperProjectPanelsSource.matchAll(/<iframe\b/g),
].length;
const devModeBranchIndex = aiGameCreatorShellAppSource.indexOf('{devMode ? (');
const developerPaneIndex = aiGameCreatorShellAppSource.indexOf('className="developer-pane"');
if (previewFrameCount !== 1) {
throw new Error('AI game creator developer pane preview frame count drifted');
}
if (developerProjectPanelIndexes.length !== 1) {
throw new Error('AI game creator developer project panels mount count drifted');
}
if (
devModeBranchIndex < 0 ||
developerPaneIndex < 0 ||
developerProjectPanelIndexes.some(
(index) => index < devModeBranchIndex || index < developerPaneIndex,
)
) {
throw new Error('AI game creator preview panels must stay inside the dev-only pane');
}
// 上面几条只约束 DeveloperProjectPanels 自身的 iframe 数量和挂载位置,管不到 App.tsx
// 直接内嵌 iframe 的情况——预览必须一律委托给客户端工作台,外壳自己不持有预览框。
if ([...aiGameCreatorShellAppSource.matchAll(/<iframe\b/g)].length !== 0) {
throw new Error(
'AI game creator app shell must delegate preview iframe to the client workbench',
);