@things-factory/board-ai 10.0.0-beta.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLAN-styling-tools.md +1253 -0
- package/client/components/board-ai-chat.test.ts +149 -0
- package/client/components/board-ai-chat.ts +2574 -0
- package/client/components/chat-echo-dedup.test.ts +116 -0
- package/client/components/chat-echo-dedup.ts +89 -0
- package/client/components/chat-input-builder.ts +47 -0
- package/client/components/default-catalog-entries.test.ts +59 -0
- package/client/components/default-catalog-entries.ts +100 -0
- package/client/components/default-slash-templates.test.ts +55 -0
- package/client/components/default-slash-templates.ts +129 -0
- package/client/components/markdown.test.ts +518 -0
- package/client/components/markdown.ts +508 -0
- package/client/components/mention-popup-helpers.ts +433 -0
- package/client/components/mention-popup.test.ts +588 -0
- package/client/components/mention-popup.ts +238 -0
- package/client/index.ts +11 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/board-edit-patch.ts +374 -0
- package/config/config.development.js +43 -0
- package/config/config.production.js +15 -0
- package/dist-client/client/components/board-ai-chat.d.ts +277 -0
- package/dist-client/client/components/board-ai-chat.js +2444 -0
- package/dist-client/client/components/board-ai-chat.js.map +1 -0
- package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
- package/dist-client/client/components/board-ai-chat.test.js +138 -0
- package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
- package/dist-client/client/components/chat-echo-dedup.js +58 -0
- package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
- package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
- package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
- package/dist-client/client/components/chat-input-builder.d.ts +39 -0
- package/dist-client/client/components/chat-input-builder.js +26 -0
- package/dist-client/client/components/chat-input-builder.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
- package/dist-client/client/components/default-catalog-entries.js +86 -0
- package/dist-client/client/components/default-catalog-entries.js.map +1 -0
- package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
- package/dist-client/client/components/default-catalog-entries.test.js +52 -0
- package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.d.ts +17 -0
- package/dist-client/client/components/default-slash-templates.js +113 -0
- package/dist-client/client/components/default-slash-templates.js.map +1 -0
- package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
- package/dist-client/client/components/default-slash-templates.test.js +51 -0
- package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
- package/dist-client/client/components/markdown.d.ts +60 -0
- package/dist-client/client/components/markdown.js +422 -0
- package/dist-client/client/components/markdown.js.map +1 -0
- package/dist-client/client/components/markdown.test.d.ts +1 -0
- package/dist-client/client/components/markdown.test.js +418 -0
- package/dist-client/client/components/markdown.test.js.map +1 -0
- package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
- package/dist-client/client/components/mention-popup-helpers.js +330 -0
- package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
- package/dist-client/client/components/mention-popup.d.ts +35 -0
- package/dist-client/client/components/mention-popup.js +223 -0
- package/dist-client/client/components/mention-popup.js.map +1 -0
- package/dist-client/client/components/mention-popup.test.d.ts +1 -0
- package/dist-client/client/components/mention-popup.test.js +474 -0
- package/dist-client/client/components/mention-popup.test.js.map +1 -0
- package/dist-client/client/index.d.ts +11 -0
- package/dist-client/client/index.js +12 -0
- package/dist-client/client/index.js.map +1 -0
- package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
- package/dist-client/client/utils/board-edit-patch.js +284 -0
- package/dist-client/client/utils/board-edit-patch.js.map +1 -0
- package/dist-client/server/service/agentic-loop.d.ts +87 -0
- package/dist-client/server/service/agentic-loop.js +276 -0
- package/dist-client/server/service/agentic-loop.js.map +1 -0
- package/dist-client/server/service/assistant.d.ts +84 -0
- package/dist-client/server/service/assistant.js +2220 -0
- package/dist-client/server/service/assistant.js.map +1 -0
- package/dist-client/server/service/catalog-resolver.d.ts +18 -0
- package/dist-client/server/service/catalog-resolver.js +93 -0
- package/dist-client/server/service/catalog-resolver.js.map +1 -0
- package/dist-client/server/service/component-tree.d.ts +68 -0
- package/dist-client/server/service/component-tree.js +68 -0
- package/dist-client/server/service/component-tree.js.map +1 -0
- package/dist-client/server/service/mention-resolver.d.ts +96 -0
- package/dist-client/server/service/mention-resolver.js +172 -0
- package/dist-client/server/service/mention-resolver.js.map +1 -0
- package/dist-client/server/service/prefab-registry.d.ts +73 -0
- package/dist-client/server/service/prefab-registry.js +252 -0
- package/dist-client/server/service/prefab-registry.js.map +1 -0
- package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
- package/dist-client/server/service/styling/clear-tools.js +50 -0
- package/dist-client/server/service/styling/clear-tools.js.map +1 -0
- package/dist-client/server/service/styling/color-utils.d.ts +43 -0
- package/dist-client/server/service/styling/color-utils.js +205 -0
- package/dist-client/server/service/styling/color-utils.js.map +1 -0
- package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
- package/dist-client/server/service/styling/copy-tools.js +78 -0
- package/dist-client/server/service/styling/copy-tools.js.map +1 -0
- package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
- package/dist-client/server/service/styling/effect-tools.js +85 -0
- package/dist-client/server/service/styling/effect-tools.js.map +1 -0
- package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
- package/dist-client/server/service/styling/fill-tools.js +226 -0
- package/dist-client/server/service/styling/fill-tools.js.map +1 -0
- package/dist-client/server/service/styling/material-tools.d.ts +32 -0
- package/dist-client/server/service/styling/material-tools.js +88 -0
- package/dist-client/server/service/styling/material-tools.js.map +1 -0
- package/dist-client/server/service/styling/read-tools.d.ts +34 -0
- package/dist-client/server/service/styling/read-tools.js +42 -0
- package/dist-client/server/service/styling/read-tools.js.map +1 -0
- package/dist-client/server/service/styling/registry.d.ts +91 -0
- package/dist-client/server/service/styling/registry.js +345 -0
- package/dist-client/server/service/styling/registry.js.map +1 -0
- package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-client/server/service/styling/stroke-tools.js +94 -0
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
- package/dist-client/server/service/styling/text-tools.d.ts +32 -0
- package/dist-client/server/service/styling/text-tools.js +101 -0
- package/dist-client/server/service/styling/text-tools.js.map +1 -0
- package/dist-client/server/service/types.d.ts +265 -0
- package/dist-client/server/service/types.js +2 -0
- package/dist-client/server/service/types.js.map +1 -0
- package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-client/server/service/validation/board-model-schema.js +402 -0
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
- package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
- package/dist-client/server/service/validation/tool-validation.js +193 -0
- package/dist-client/server/service/validation/tool-validation.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/index.d.ts +23 -0
- package/dist-server/index.js +27 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/agentic-loop.d.ts +87 -0
- package/dist-server/service/agentic-loop.js +280 -0
- package/dist-server/service/agentic-loop.js.map +1 -0
- package/dist-server/service/apply-patch.d.ts +47 -0
- package/dist-server/service/apply-patch.js +369 -0
- package/dist-server/service/apply-patch.js.map +1 -0
- package/dist-server/service/assistant.d.ts +84 -0
- package/dist-server/service/assistant.js +2237 -0
- package/dist-server/service/assistant.js.map +1 -0
- package/dist-server/service/board-ai-resolver.d.ts +54 -0
- package/dist-server/service/board-ai-resolver.js +409 -0
- package/dist-server/service/board-ai-resolver.js.map +1 -0
- package/dist-server/service/catalog-resolver.d.ts +18 -0
- package/dist-server/service/catalog-resolver.js +98 -0
- package/dist-server/service/catalog-resolver.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
- package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
- package/dist-server/service/chat-message/chat-message-publish.js +27 -0
- package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
- package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
- package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-message.d.ts +56 -0
- package/dist-server/service/chat-message/chat-message.js +149 -0
- package/dist-server/service/chat-message/chat-message.js.map +1 -0
- package/dist-server/service/chat-message/index.d.ts +5 -0
- package/dist-server/service/chat-message/index.js +12 -0
- package/dist-server/service/chat-message/index.js.map +1 -0
- package/dist-server/service/chat-session/chat-session.d.ts +31 -0
- package/dist-server/service/chat-session/chat-session.js +131 -0
- package/dist-server/service/chat-session/chat-session.js.map +1 -0
- package/dist-server/service/chat-session/index.d.ts +3 -0
- package/dist-server/service/chat-session/index.js +7 -0
- package/dist-server/service/chat-session/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
- package/dist-server/service/chat-session-participant/index.d.ts +4 -0
- package/dist-server/service/chat-session-participant/index.js +10 -0
- package/dist-server/service/chat-session-participant/index.js.map +1 -0
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
- package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
- package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
- package/dist-server/service/chat-session-participant/presence.js +22 -0
- package/dist-server/service/chat-session-participant/presence.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +20 -0
- package/dist-server/service/chat-session-resolver.js +356 -0
- package/dist-server/service/chat-session-resolver.js.map +1 -0
- package/dist-server/service/component-tree.d.ts +68 -0
- package/dist-server/service/component-tree.js +73 -0
- package/dist-server/service/component-tree.js.map +1 -0
- package/dist-server/service/index.d.ts +20 -0
- package/dist-server/service/index.js +34 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/mention-marker.d.ts +78 -0
- package/dist-server/service/mention-marker.js +162 -0
- package/dist-server/service/mention-marker.js.map +1 -0
- package/dist-server/service/mention-resolver.d.ts +96 -0
- package/dist-server/service/mention-resolver.js +177 -0
- package/dist-server/service/mention-resolver.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
- package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
- package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
- package/dist-server/service/patch-entry/index.d.ts +4 -0
- package/dist-server/service/patch-entry/index.js +12 -0
- package/dist-server/service/patch-entry/index.js.map +1 -0
- package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
- package/dist-server/service/patch-entry/patch-entry.js +96 -0
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
- package/dist-server/service/prefab-registry.d.ts +73 -0
- package/dist-server/service/prefab-registry.js +258 -0
- package/dist-server/service/prefab-registry.js.map +1 -0
- package/dist-server/service/styling/clear-tools.d.ts +15 -0
- package/dist-server/service/styling/clear-tools.js +53 -0
- package/dist-server/service/styling/clear-tools.js.map +1 -0
- package/dist-server/service/styling/color-utils.d.ts +43 -0
- package/dist-server/service/styling/color-utils.js +210 -0
- package/dist-server/service/styling/color-utils.js.map +1 -0
- package/dist-server/service/styling/copy-tools.d.ts +27 -0
- package/dist-server/service/styling/copy-tools.js +81 -0
- package/dist-server/service/styling/copy-tools.js.map +1 -0
- package/dist-server/service/styling/effect-tools.d.ts +45 -0
- package/dist-server/service/styling/effect-tools.js +90 -0
- package/dist-server/service/styling/effect-tools.js.map +1 -0
- package/dist-server/service/styling/fill-tools.d.ts +60 -0
- package/dist-server/service/styling/fill-tools.js +229 -0
- package/dist-server/service/styling/fill-tools.js.map +1 -0
- package/dist-server/service/styling/material-tools.d.ts +32 -0
- package/dist-server/service/styling/material-tools.js +92 -0
- package/dist-server/service/styling/material-tools.js.map +1 -0
- package/dist-server/service/styling/read-tools.d.ts +34 -0
- package/dist-server/service/styling/read-tools.js +45 -0
- package/dist-server/service/styling/read-tools.js.map +1 -0
- package/dist-server/service/styling/registry.d.ts +91 -0
- package/dist-server/service/styling/registry.js +352 -0
- package/dist-server/service/styling/registry.js.map +1 -0
- package/dist-server/service/styling/stroke-tools.d.ts +35 -0
- package/dist-server/service/styling/stroke-tools.js +97 -0
- package/dist-server/service/styling/stroke-tools.js.map +1 -0
- package/dist-server/service/styling/text-tools.d.ts +32 -0
- package/dist-server/service/styling/text-tools.js +105 -0
- package/dist-server/service/styling/text-tools.js.map +1 -0
- package/dist-server/service/subscribe-auth.d.ts +14 -0
- package/dist-server/service/subscribe-auth.js +33 -0
- package/dist-server/service/subscribe-auth.js.map +1 -0
- package/dist-server/service/types.d.ts +265 -0
- package/dist-server/service/types.js +3 -0
- package/dist-server/service/types.js.map +1 -0
- package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
- package/dist-server/service/validation/board-model-schema.js +409 -0
- package/dist-server/service/validation/board-model-schema.js.map +1 -0
- package/dist-server/service/validation/tool-validation.d.ts +31 -0
- package/dist-server/service/validation/tool-validation.js +196 -0
- package/dist-server/service/validation/tool-validation.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +48 -0
- package/server/index.ts +23 -0
- package/server/service/agentic-loop.test.ts +705 -0
- package/server/service/agentic-loop.ts +431 -0
- package/server/service/apply-patch-drift.test.ts +829 -0
- package/server/service/apply-patch.test.ts +757 -0
- package/server/service/apply-patch.ts +411 -0
- package/server/service/assistant-integration.test.ts +922 -0
- package/server/service/assistant-llm-smoke.test.ts +296 -0
- package/server/service/assistant.test.ts +2034 -0
- package/server/service/assistant.ts +2378 -0
- package/server/service/board-ai-resolver.ts +407 -0
- package/server/service/catalog-resolver.test.ts +137 -0
- package/server/service/catalog-resolver.ts +102 -0
- package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
- package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
- package/server/service/chat-message/chat-message-broadcast.ts +27 -0
- package/server/service/chat-message/chat-message-publish.ts +22 -0
- package/server/service/chat-message/chat-message-subscription.ts +41 -0
- package/server/service/chat-message/chat-message.ts +169 -0
- package/server/service/chat-message/index.ts +12 -0
- package/server/service/chat-session/chat-session.ts +130 -0
- package/server/service/chat-session/index.ts +5 -0
- package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
- package/server/service/chat-session-participant/index.ts +6 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
- package/server/service/chat-session-participant/invite-notification.ts +48 -0
- package/server/service/chat-session-participant/presence.test.ts +39 -0
- package/server/service/chat-session-participant/presence.ts +21 -0
- package/server/service/chat-session-resolver.ts +312 -0
- package/server/service/component-tree.test.ts +248 -0
- package/server/service/component-tree.ts +135 -0
- package/server/service/index.ts +32 -0
- package/server/service/mention-marker.extract.test.ts +61 -0
- package/server/service/mention-marker.test.ts +228 -0
- package/server/service/mention-marker.ts +184 -0
- package/server/service/mention-resolver.test.ts +273 -0
- package/server/service/mention-resolver.ts +264 -0
- package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
- package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
- package/server/service/patch-entry/board-patch-subscription.ts +50 -0
- package/server/service/patch-entry/index.ts +12 -0
- package/server/service/patch-entry/patch-entry.ts +89 -0
- package/server/service/prefab-registry.test.ts +172 -0
- package/server/service/prefab-registry.ts +332 -0
- package/server/service/styling/clear-tools.test.ts +164 -0
- package/server/service/styling/clear-tools.ts +69 -0
- package/server/service/styling/color-utils.test.ts +130 -0
- package/server/service/styling/color-utils.ts +226 -0
- package/server/service/styling/copy-tools.test.ts +152 -0
- package/server/service/styling/copy-tools.ts +115 -0
- package/server/service/styling/effect-tools.test.ts +277 -0
- package/server/service/styling/effect-tools.ts +124 -0
- package/server/service/styling/fill-tools.test.ts +331 -0
- package/server/service/styling/fill-tools.ts +293 -0
- package/server/service/styling/material-tools.test.ts +294 -0
- package/server/service/styling/material-tools.ts +114 -0
- package/server/service/styling/read-tools.test.ts +78 -0
- package/server/service/styling/read-tools.ts +67 -0
- package/server/service/styling/registry.test.ts +203 -0
- package/server/service/styling/registry.ts +423 -0
- package/server/service/styling/stroke-tools.test.ts +245 -0
- package/server/service/styling/stroke-tools.ts +145 -0
- package/server/service/styling/text-tools.test.ts +253 -0
- package/server/service/styling/text-tools.ts +131 -0
- package/server/service/styling/tier1-tools.test.ts +300 -0
- package/server/service/subscribe-auth.test.ts +57 -0
- package/server/service/subscribe-auth.ts +34 -0
- package/server/service/types.ts +235 -0
- package/server/service/validation/board-model-schema.test.ts +778 -0
- package/server/service/validation/board-model-schema.ts +446 -0
- package/server/service/validation/tool-validation.ts +228 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +49 -0
- package/translations/ja.json +48 -0
- package/translations/ko.json +49 -0
- package/translations/ms.json +48 -0
- package/translations/zh.json +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @things-factory/board-ai — AI 주도 보드 모델링.
|
|
3
|
+
*
|
|
4
|
+
* 단일 진입점 `chat()` 으로 보드 생성·편집·스타일링을 자연어로 통합 처리.
|
|
5
|
+
* board-import 의 BoardModel 위에서 동작하며, ai-client-base 가 LLM provider 추상화 담당.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createAIClient } from '@things-factory/ai-client-base'
|
|
12
|
+
* import { DefaultBoardAIAssistant, applyBoardEditPatch } from '@things-factory/board-ai'
|
|
13
|
+
*
|
|
14
|
+
* const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
|
|
15
|
+
* const ai = new DefaultBoardAIAssistant(base, { scopes: ['fmsim'] })
|
|
16
|
+
*
|
|
17
|
+
* let board
|
|
18
|
+
* const r = await ai.chat([{ role: 'user', content: 'AGV 3대를 우측에 추가' }], board)
|
|
19
|
+
* if (r.patch) board = applyBoardEditPatch(board, r.patch)
|
|
20
|
+
* console.log(r.reply)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export * from './service/index.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* @things-factory/board-ai — AI 주도 보드 모델링.
|
|
6
|
+
*
|
|
7
|
+
* 단일 진입점 `chat()` 으로 보드 생성·편집·스타일링을 자연어로 통합 처리.
|
|
8
|
+
* board-import 의 BoardModel 위에서 동작하며, ai-client-base 가 LLM provider 추상화 담당.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createAIClient } from '@things-factory/ai-client-base'
|
|
15
|
+
* import { DefaultBoardAIAssistant, applyBoardEditPatch } from '@things-factory/board-ai'
|
|
16
|
+
*
|
|
17
|
+
* const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
|
|
18
|
+
* const ai = new DefaultBoardAIAssistant(base, { scopes: ['fmsim'] })
|
|
19
|
+
*
|
|
20
|
+
* let board
|
|
21
|
+
* const r = await ai.chat([{ role: 'user', content: 'AGV 3대를 우측에 추가' }], board)
|
|
22
|
+
* if (r.patch) board = applyBoardEditPatch(board, r.patch)
|
|
23
|
+
* console.log(r.reply)
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
tslib_1.__exportStar(require("./service/index.js"), exports);
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,6DAAkC","sourcesContent":["/**\n * @things-factory/board-ai — AI 주도 보드 모델링.\n *\n * 단일 진입점 `chat()` 으로 보드 생성·편집·스타일링을 자연어로 통합 처리.\n * board-import 의 BoardModel 위에서 동작하며, ai-client-base 가 LLM provider 추상화 담당.\n *\n * @public\n *\n * @example\n * ```ts\n * import { createAIClient } from '@things-factory/ai-client-base'\n * import { DefaultBoardAIAssistant, applyBoardEditPatch } from '@things-factory/board-ai'\n *\n * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })\n * const ai = new DefaultBoardAIAssistant(base, { scopes: ['fmsim'] })\n *\n * let board\n * const r = await ai.chat([{ role: 'user', content: 'AGV 3대를 우측에 추가' }], board)\n * if (r.patch) board = applyBoardEditPatch(board, r.patch)\n * console.log(r.reply)\n * ```\n */\nexport * from './service/index.js'\n"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { AIMessage, AIToolCall, AIToolChatResult } from '@things-factory/ai-client-base';
|
|
2
|
+
import type { BoardModel } from '@things-factory/board-import';
|
|
3
|
+
import type { BoardActionOp, ToolUsage } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Loop 가 호출하는 dispatch 함수들. 모두 의존성 주입.
|
|
6
|
+
*
|
|
7
|
+
* assistant.ts 가 자기 자신의 함수 (isReadTool, executeReadTool 등) 를 그대로
|
|
8
|
+
* 넘겨주는 구조. 본 모듈은 이 함수들의 *시그니처* 만 알면 됨.
|
|
9
|
+
*/
|
|
10
|
+
export interface ToolDispatchHelpers {
|
|
11
|
+
isReadTool: (name: string) => boolean;
|
|
12
|
+
isWriteTool: (name: string) => boolean;
|
|
13
|
+
isActionTool: (name: string) => boolean;
|
|
14
|
+
executeReadTool: (tc: AIToolCall, board: BoardModel | undefined, selectedRefids: number[]) => any;
|
|
15
|
+
validateWriteToolCall: (tc: AIToolCall, components: any[]) => {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
errors?: any[];
|
|
18
|
+
suggestion?: string;
|
|
19
|
+
};
|
|
20
|
+
toolCallToBoardActionOp: (tc: AIToolCall) => BoardActionOp | null;
|
|
21
|
+
summarizeToolResult: (value: any) => any;
|
|
22
|
+
}
|
|
23
|
+
export interface AgenticLoopOptions {
|
|
24
|
+
systemPrompt: string;
|
|
25
|
+
model?: string;
|
|
26
|
+
temperature?: number;
|
|
27
|
+
maxTokens?: number;
|
|
28
|
+
/** 최대 iteration. 기본 8. */
|
|
29
|
+
maxIterations?: number;
|
|
30
|
+
/**
|
|
31
|
+
* 사용자 abort 신호. iteration 시작 직전 / chat() 호출 후 체크.
|
|
32
|
+
* 이미 abort 된 신호로 들어오면 첫 iteration 도 안 돔.
|
|
33
|
+
*/
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* 같은 tool 의 validation 실패가 N회 연속이면 조기 종료. 기본 3.
|
|
37
|
+
* LLM 이 같은 잘못을 무한 반복하는 경우 방어.
|
|
38
|
+
*/
|
|
39
|
+
repeatedFailureLimit?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface AgenticLoopInput {
|
|
42
|
+
initialConversation: AIMessage[];
|
|
43
|
+
tools: any[];
|
|
44
|
+
options: AgenticLoopOptions;
|
|
45
|
+
/** LLM provider 의 chatWithTools 위임. provider-specific 구현. */
|
|
46
|
+
chat: (messages: AIMessage[], tools: any[], options: any) => Promise<AIToolChatResult>;
|
|
47
|
+
dispatch: ToolDispatchHelpers;
|
|
48
|
+
currentBoard: BoardModel | undefined;
|
|
49
|
+
selectedRefids: number[];
|
|
50
|
+
}
|
|
51
|
+
export interface AgenticLoopResult {
|
|
52
|
+
lastText: string | undefined;
|
|
53
|
+
accumulatedWriteCalls: AIToolCall[];
|
|
54
|
+
accumulatedActions: BoardActionOp[];
|
|
55
|
+
toolUsages: ToolUsage[];
|
|
56
|
+
/** LLM 마지막 응답의 stopReason. provider-specific 문자열. */
|
|
57
|
+
stopReason: string;
|
|
58
|
+
/**
|
|
59
|
+
* 비정상 종료 사유 — 정상 종료 시 undefined.
|
|
60
|
+
* 호출자 (assistant.ts) 가 사용자 메시지에 반영 (예: '시도 중 오류가 발생했어요').
|
|
61
|
+
*/
|
|
62
|
+
abortReason?: {
|
|
63
|
+
type: 'provider_error';
|
|
64
|
+
message: string;
|
|
65
|
+
iter: number;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'aborted';
|
|
68
|
+
iter: number;
|
|
69
|
+
} | {
|
|
70
|
+
type: 'max_iterations';
|
|
71
|
+
iter: number;
|
|
72
|
+
} | {
|
|
73
|
+
type: 'repeated_validation_failure';
|
|
74
|
+
toolName: string;
|
|
75
|
+
consecutive: number;
|
|
76
|
+
iter: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* multi-turn agentic loop.
|
|
81
|
+
*
|
|
82
|
+
* LLM 이 read tool 을 호출 → 서버에서 즉시 실행 → 결과 회신 → LLM 추론 반복.
|
|
83
|
+
* write tool 은 누적해 client 에 patch 로 전달. 무한 루프 방지를 위해 iteration cap.
|
|
84
|
+
*
|
|
85
|
+
* 본 함수는 *순수* — input 만 보고 결정. 외부 mutable state 없음.
|
|
86
|
+
*/
|
|
87
|
+
export declare function runAgenticLoop(input: AgenticLoopInput): Promise<AgenticLoopResult>;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAgenticLoop = runAgenticLoop;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* agentic-loop — multi-turn LLM ↔ tool 루프.
|
|
7
|
+
*
|
|
8
|
+
* assistant.ts 에서 추출 — loop 자체를 *순수 함수* 로 만들어:
|
|
9
|
+
* 1. 단위 테스트 가능 (assistant.ts 의 board-import → @aws-sdk transitive 경로 회피)
|
|
10
|
+
* 2. 관측성 (error boundary / iter log / AbortSignal / repeated-failure 조기 종료)
|
|
11
|
+
* 을 한 곳에 응집
|
|
12
|
+
* 3. 향후 split (Phase 3) 의 자연 경계
|
|
13
|
+
*
|
|
14
|
+
* 입력 — `chat` 함수와 dispatch helper 들을 의존성 주입. board-ai 본체는 본 모듈에
|
|
15
|
+
* 의존하지만 본 모듈은 board-ai 의 어떤 무거운 import 도 모름.
|
|
16
|
+
*/
|
|
17
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
18
|
+
const log = (0, debug_1.default)('things-factory:board-ai:loop');
|
|
19
|
+
// ── Loop ─────────────────────────────────────────────────────────
|
|
20
|
+
const DEFAULT_MAX_ITERATIONS = 8;
|
|
21
|
+
/**
|
|
22
|
+
* multi-turn agentic loop.
|
|
23
|
+
*
|
|
24
|
+
* LLM 이 read tool 을 호출 → 서버에서 즉시 실행 → 결과 회신 → LLM 추론 반복.
|
|
25
|
+
* write tool 은 누적해 client 에 patch 로 전달. 무한 루프 방지를 위해 iteration cap.
|
|
26
|
+
*
|
|
27
|
+
* 본 함수는 *순수* — input 만 보고 결정. 외부 mutable state 없음.
|
|
28
|
+
*/
|
|
29
|
+
async function runAgenticLoop(input) {
|
|
30
|
+
const { initialConversation, tools, options, chat, dispatch, currentBoard, selectedRefids } = input;
|
|
31
|
+
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
32
|
+
const failureLimit = options.repeatedFailureLimit ?? 3;
|
|
33
|
+
const signal = options.signal;
|
|
34
|
+
let conversation = initialConversation;
|
|
35
|
+
const accumulatedWriteCalls = [];
|
|
36
|
+
const accumulatedActions = [];
|
|
37
|
+
const toolUsages = [];
|
|
38
|
+
let lastText;
|
|
39
|
+
let stopReason = 'end_turn';
|
|
40
|
+
let abortReason;
|
|
41
|
+
// 같은 tool 의 validation 실패 연속 카운터 — 다른 tool 호출 시 reset
|
|
42
|
+
let lastFailedToolName;
|
|
43
|
+
let consecutiveFailures = 0;
|
|
44
|
+
let iter = 0;
|
|
45
|
+
for (; iter < maxIterations; iter++) {
|
|
46
|
+
// (1) AbortSignal — iteration 시작 시 체크
|
|
47
|
+
if (signal?.aborted) {
|
|
48
|
+
log('iter %d: aborted by signal', iter);
|
|
49
|
+
abortReason = { type: 'aborted', iter };
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
log('iter %d: chat() 호출', iter);
|
|
53
|
+
let result;
|
|
54
|
+
try {
|
|
55
|
+
// (2) Error boundary — provider 예외 시 graceful 종료
|
|
56
|
+
result = await chat(conversation, tools, {
|
|
57
|
+
systemPrompt: options.systemPrompt,
|
|
58
|
+
model: options.model,
|
|
59
|
+
temperature: options.temperature,
|
|
60
|
+
maxTokens: options.maxTokens,
|
|
61
|
+
toolChoice: 'auto',
|
|
62
|
+
allowParallelToolCalls: true,
|
|
63
|
+
signal
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
// AbortError — 별도 분류
|
|
68
|
+
if (e?.name === 'AbortError' || signal?.aborted) {
|
|
69
|
+
log('iter %d: provider AbortError', iter);
|
|
70
|
+
abortReason = { type: 'aborted', iter };
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
log('iter %d: provider error: %s', iter, e?.message);
|
|
74
|
+
abortReason = {
|
|
75
|
+
type: 'provider_error',
|
|
76
|
+
message: e?.message ?? String(e),
|
|
77
|
+
iter
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
lastText = result.text || lastText;
|
|
83
|
+
stopReason = result.stopReason;
|
|
84
|
+
log('iter %d: text=%s, toolCalls=%d, stopReason=%s', iter, result.text ? `"${result.text.slice(0, 60)}..."` : '(none)', result.toolCalls.length, result.stopReason);
|
|
85
|
+
if (result.toolCalls.length === 0)
|
|
86
|
+
break;
|
|
87
|
+
// (1b) 삭제: chat() 직후 abort 는 다음 iter 시작 시 (1) 에서 감지.
|
|
88
|
+
// 현재 iter tool 실행을 중단하면 read tool 결과도 버려져 테스트/동작 불일치.
|
|
89
|
+
// tool 결과 빌드 — read 는 즉시 실행, write 는 누적, action 은 별도 채널
|
|
90
|
+
const beforeUsages = toolUsages.length;
|
|
91
|
+
const toolResultParts = await processToolCalls(result.toolCalls, currentBoard, selectedRefids, dispatch, accumulatedWriteCalls, accumulatedActions, toolUsages);
|
|
92
|
+
// (4) 같은 tool 의 validation 실패 연속 감지 — LLM 이 같은 잘못 반복 시 abort
|
|
93
|
+
const newUsages = toolUsages.slice(beforeUsages);
|
|
94
|
+
const repeatedFailureAbort = detectRepeatedValidationFailure(newUsages, lastFailedToolName, consecutiveFailures, failureLimit);
|
|
95
|
+
if (repeatedFailureAbort.shouldAbort) {
|
|
96
|
+
log('iter %d: repeated validation failure on %s (%d consecutive) — abort', iter, repeatedFailureAbort.toolName, repeatedFailureAbort.consecutive);
|
|
97
|
+
abortReason = {
|
|
98
|
+
type: 'repeated_validation_failure',
|
|
99
|
+
toolName: repeatedFailureAbort.toolName,
|
|
100
|
+
consecutive: repeatedFailureAbort.consecutive,
|
|
101
|
+
iter
|
|
102
|
+
};
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
lastFailedToolName = repeatedFailureAbort.lastFailedToolName;
|
|
106
|
+
consecutiveFailures = repeatedFailureAbort.consecutive;
|
|
107
|
+
// assistant 의 tool_use turn + user 의 tool_result turn 을 conversation 에 누적
|
|
108
|
+
const assistantContent = [];
|
|
109
|
+
if (result.text)
|
|
110
|
+
assistantContent.push({ type: 'text', text: result.text });
|
|
111
|
+
for (const tc of result.toolCalls) {
|
|
112
|
+
// providerMeta (Gemini thoughtSignature 등) 를 그대로 round-trip — 안 보내면 400.
|
|
113
|
+
assistantContent.push({
|
|
114
|
+
type: 'tool_use',
|
|
115
|
+
id: tc.id,
|
|
116
|
+
name: tc.name,
|
|
117
|
+
arguments: tc.arguments,
|
|
118
|
+
...(tc.providerMeta && { providerMeta: tc.providerMeta })
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
conversation = [
|
|
122
|
+
...conversation,
|
|
123
|
+
{ role: 'assistant', content: assistantContent },
|
|
124
|
+
{ role: 'user', content: toolResultParts }
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
// (3) max iterations 도달 시 사용자에게 표면화
|
|
128
|
+
if (iter >= maxIterations && !abortReason) {
|
|
129
|
+
log('iter %d: max iterations reached', iter);
|
|
130
|
+
abortReason = { type: 'max_iterations', iter };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
lastText,
|
|
134
|
+
accumulatedWriteCalls,
|
|
135
|
+
accumulatedActions,
|
|
136
|
+
toolUsages,
|
|
137
|
+
stopReason,
|
|
138
|
+
abortReason
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 같은 tool 의 validation 실패 연속 감지 — pure helper.
|
|
143
|
+
*
|
|
144
|
+
* 같은 tool 이 반복 fail 하면 LLM 이 self-correct 못 하는 상태.
|
|
145
|
+
* 다른 tool 이 fail 하면 시퀀스 새로 시작 (전혀 다른 패턴이라).
|
|
146
|
+
* tool 이 succeed 하면 카운터 reset.
|
|
147
|
+
*/
|
|
148
|
+
function detectRepeatedValidationFailure(newUsages, lastFailedToolName, consecutiveFailures, limit) {
|
|
149
|
+
// 이번 iteration 에서 실패한 write tool 들 (validation 거절)
|
|
150
|
+
const failed = newUsages.filter(u => u.kind === 'write' && u.result?.error === 'Tool args failed validation');
|
|
151
|
+
if (failed.length === 0) {
|
|
152
|
+
// 실패 없음 — reset
|
|
153
|
+
return {
|
|
154
|
+
shouldAbort: false,
|
|
155
|
+
toolName: '',
|
|
156
|
+
consecutive: 0,
|
|
157
|
+
lastFailedToolName: undefined
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// 모든 실패가 같은 tool 인지 확인
|
|
161
|
+
const firstName = failed[0].name;
|
|
162
|
+
const allSame = failed.every(u => u.name === firstName);
|
|
163
|
+
if (allSame && firstName === lastFailedToolName) {
|
|
164
|
+
const consecutive = consecutiveFailures + 1;
|
|
165
|
+
return {
|
|
166
|
+
shouldAbort: consecutive >= limit,
|
|
167
|
+
toolName: firstName,
|
|
168
|
+
consecutive,
|
|
169
|
+
lastFailedToolName: firstName
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// 다른 tool 또는 첫 실패
|
|
173
|
+
return {
|
|
174
|
+
shouldAbort: false,
|
|
175
|
+
toolName: firstName,
|
|
176
|
+
consecutive: 1,
|
|
177
|
+
lastFailedToolName: firstName
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 한 iteration 의 tool 결과를 분류·실행·누적. (loop 에서 분리해 읽기 쉽게.)
|
|
182
|
+
*/
|
|
183
|
+
async function processToolCalls(toolCalls, currentBoard, selectedRefids, dispatch, accumulatedWriteCalls, accumulatedActions, toolUsages) {
|
|
184
|
+
const toolResultParts = [];
|
|
185
|
+
for (const tc of toolCalls) {
|
|
186
|
+
if (dispatch.isReadTool(tc.name)) {
|
|
187
|
+
const value = dispatch.executeReadTool(tc, currentBoard, selectedRefids);
|
|
188
|
+
toolResultParts.push({
|
|
189
|
+
type: 'tool_result',
|
|
190
|
+
toolUseId: tc.id,
|
|
191
|
+
content: JSON.stringify(value)
|
|
192
|
+
});
|
|
193
|
+
toolUsages.push({
|
|
194
|
+
name: tc.name,
|
|
195
|
+
arguments: tc.arguments ?? {},
|
|
196
|
+
result: dispatch.summarizeToolResult(value),
|
|
197
|
+
kind: 'read'
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else if (dispatch.isWriteTool(tc.name)) {
|
|
201
|
+
// 사전 검증 — args 가 schema / build 함수 검증 통과해야 누적.
|
|
202
|
+
// 실패 시 LLM 한테 detailed error 반환 → 다음 turn 에서 자기 회복 (retry).
|
|
203
|
+
const components = (currentBoard?.components ?? []);
|
|
204
|
+
const validation = dispatch.validateWriteToolCall(tc, components);
|
|
205
|
+
if (!validation.valid) {
|
|
206
|
+
const errorResult = {
|
|
207
|
+
error: 'Tool args failed validation',
|
|
208
|
+
issues: validation.errors,
|
|
209
|
+
suggestion: validation.suggestion,
|
|
210
|
+
note: 'Fix the args and call the tool again.'
|
|
211
|
+
};
|
|
212
|
+
toolResultParts.push({
|
|
213
|
+
type: 'tool_result',
|
|
214
|
+
toolUseId: tc.id,
|
|
215
|
+
content: JSON.stringify(errorResult)
|
|
216
|
+
});
|
|
217
|
+
toolUsages.push({
|
|
218
|
+
name: tc.name,
|
|
219
|
+
arguments: tc.arguments ?? {},
|
|
220
|
+
result: errorResult,
|
|
221
|
+
kind: 'write'
|
|
222
|
+
});
|
|
223
|
+
// continue — 누적 안 함. LLM 이 다음 turn 에서 정정 호출.
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
accumulatedWriteCalls.push(tc);
|
|
227
|
+
const queuedResult = { queued: true, note: 'Will be applied on the client.' };
|
|
228
|
+
toolResultParts.push({
|
|
229
|
+
type: 'tool_result',
|
|
230
|
+
toolUseId: tc.id,
|
|
231
|
+
content: JSON.stringify(queuedResult)
|
|
232
|
+
});
|
|
233
|
+
toolUsages.push({
|
|
234
|
+
name: tc.name,
|
|
235
|
+
arguments: tc.arguments ?? {},
|
|
236
|
+
result: queuedResult,
|
|
237
|
+
kind: 'write'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (dispatch.isActionTool(tc.name)) {
|
|
242
|
+
// ephemeral scene 조작 — accumulatedActions 로 누적, ChatResponse.actions 로 client 전달
|
|
243
|
+
const action = dispatch.toolCallToBoardActionOp(tc);
|
|
244
|
+
if (action)
|
|
245
|
+
accumulatedActions.push(action);
|
|
246
|
+
const queuedResult = {
|
|
247
|
+
queued: true,
|
|
248
|
+
note: 'Will be executed on the modeller (ephemeral).'
|
|
249
|
+
};
|
|
250
|
+
toolResultParts.push({
|
|
251
|
+
type: 'tool_result',
|
|
252
|
+
toolUseId: tc.id,
|
|
253
|
+
content: JSON.stringify(queuedResult)
|
|
254
|
+
});
|
|
255
|
+
toolUsages.push({
|
|
256
|
+
name: tc.name,
|
|
257
|
+
arguments: tc.arguments ?? {},
|
|
258
|
+
result: queuedResult,
|
|
259
|
+
kind: 'write'
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const errResult = { error: `Unknown tool: ${tc.name}` };
|
|
264
|
+
toolResultParts.push({
|
|
265
|
+
type: 'tool_result',
|
|
266
|
+
toolUseId: tc.id,
|
|
267
|
+
content: JSON.stringify(errResult),
|
|
268
|
+
isError: true
|
|
269
|
+
});
|
|
270
|
+
toolUsages.push({
|
|
271
|
+
name: tc.name,
|
|
272
|
+
arguments: tc.arguments ?? {},
|
|
273
|
+
result: errResult,
|
|
274
|
+
kind: 'unknown'
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return toolResultParts;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=agentic-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentic-loop.js","sourceRoot":"","sources":["../../server/service/agentic-loop.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * agentic-loop — multi-turn LLM ↔ tool 루프.\n *\n * assistant.ts 에서 추출 — loop 자체를 *순수 함수* 로 만들어:\n * 1. 단위 테스트 가능 (assistant.ts 의 board-import → @aws-sdk transitive 경로 회피)\n * 2. 관측성 (error boundary / iter log / AbortSignal / repeated-failure 조기 종료)\n * 을 한 곳에 응집\n * 3. 향후 split (Phase 3) 의 자연 경계\n *\n * 입력 — `chat` 함수와 dispatch helper 들을 의존성 주입. board-ai 본체는 본 모듈에\n * 의존하지만 본 모듈은 board-ai 의 어떤 무거운 import 도 모름.\n */\nimport debug from 'debug'\nimport type {\n AIMessage,\n AIToolCall,\n AIToolChatResult\n} from '@things-factory/ai-client-base'\nimport type { BoardModel } from '@things-factory/board-import'\nimport type { BoardActionOp, ToolUsage } from './types'\n\nconst log = debug('things-factory:board-ai:loop')\n\n// ── Public types ─────────────────────────────────────────────────\n\n/**\n * Loop 가 호출하는 dispatch 함수들. 모두 의존성 주입.\n *\n * assistant.ts 가 자기 자신의 함수 (isReadTool, executeReadTool 등) 를 그대로\n * 넘겨주는 구조. 본 모듈은 이 함수들의 *시그니처* 만 알면 됨.\n */\nexport interface ToolDispatchHelpers {\n isReadTool: (name: string) => boolean\n isWriteTool: (name: string) => boolean\n isActionTool: (name: string) => boolean\n executeReadTool: (\n tc: AIToolCall,\n board: BoardModel | undefined,\n selectedRefids: number[]\n ) => any\n validateWriteToolCall: (\n tc: AIToolCall,\n components: any[]\n ) => { valid: boolean; errors?: any[]; suggestion?: string }\n toolCallToBoardActionOp: (tc: AIToolCall) => BoardActionOp | null\n summarizeToolResult: (value: any) => any\n}\n\nexport interface AgenticLoopOptions {\n systemPrompt: string\n model?: string\n temperature?: number\n maxTokens?: number\n /** 최대 iteration. 기본 8. */\n maxIterations?: number\n /**\n * 사용자 abort 신호. iteration 시작 직전 / chat() 호출 후 체크.\n * 이미 abort 된 신호로 들어오면 첫 iteration 도 안 돔.\n */\n signal?: AbortSignal\n /**\n * 같은 tool 의 validation 실패가 N회 연속이면 조기 종료. 기본 3.\n * LLM 이 같은 잘못을 무한 반복하는 경우 방어.\n */\n repeatedFailureLimit?: number\n}\n\nexport interface AgenticLoopInput {\n initialConversation: AIMessage[]\n tools: any[]\n options: AgenticLoopOptions\n /** LLM provider 의 chatWithTools 위임. provider-specific 구현. */\n chat: (\n messages: AIMessage[],\n tools: any[],\n options: any\n ) => Promise<AIToolChatResult>\n dispatch: ToolDispatchHelpers\n currentBoard: BoardModel | undefined\n selectedRefids: number[]\n}\n\nexport interface AgenticLoopResult {\n lastText: string | undefined\n accumulatedWriteCalls: AIToolCall[]\n accumulatedActions: BoardActionOp[]\n toolUsages: ToolUsage[]\n /** LLM 마지막 응답의 stopReason. provider-specific 문자열. */\n stopReason: string\n /**\n * 비정상 종료 사유 — 정상 종료 시 undefined.\n * 호출자 (assistant.ts) 가 사용자 메시지에 반영 (예: '시도 중 오류가 발생했어요').\n */\n abortReason?:\n | { type: 'provider_error'; message: string; iter: number }\n | { type: 'aborted'; iter: number }\n | { type: 'max_iterations'; iter: number }\n | { type: 'repeated_validation_failure'; toolName: string; consecutive: number; iter: number }\n}\n\n// ── Loop ─────────────────────────────────────────────────────────\n\nconst DEFAULT_MAX_ITERATIONS = 8\n\n/**\n * multi-turn agentic loop.\n *\n * LLM 이 read tool 을 호출 → 서버에서 즉시 실행 → 결과 회신 → LLM 추론 반복.\n * write tool 은 누적해 client 에 patch 로 전달. 무한 루프 방지를 위해 iteration cap.\n *\n * 본 함수는 *순수* — input 만 보고 결정. 외부 mutable state 없음.\n */\nexport async function runAgenticLoop(\n input: AgenticLoopInput\n): Promise<AgenticLoopResult> {\n const {\n initialConversation,\n tools,\n options,\n chat,\n dispatch,\n currentBoard,\n selectedRefids\n } = input\n\n const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS\n const failureLimit = options.repeatedFailureLimit ?? 3\n const signal = options.signal\n\n let conversation = initialConversation\n const accumulatedWriteCalls: AIToolCall[] = []\n const accumulatedActions: BoardActionOp[] = []\n const toolUsages: ToolUsage[] = []\n let lastText: string | undefined\n let stopReason: string = 'end_turn'\n let abortReason: AgenticLoopResult['abortReason']\n\n // 같은 tool 의 validation 실패 연속 카운터 — 다른 tool 호출 시 reset\n let lastFailedToolName: string | undefined\n let consecutiveFailures = 0\n\n let iter = 0\n for (; iter < maxIterations; iter++) {\n // (1) AbortSignal — iteration 시작 시 체크\n if (signal?.aborted) {\n log('iter %d: aborted by signal', iter)\n abortReason = { type: 'aborted', iter }\n break\n }\n\n log('iter %d: chat() 호출', iter)\n let result: AIToolChatResult\n try {\n // (2) Error boundary — provider 예외 시 graceful 종료\n result = await chat(conversation, tools, {\n systemPrompt: options.systemPrompt,\n model: options.model,\n temperature: options.temperature,\n maxTokens: options.maxTokens,\n toolChoice: 'auto',\n allowParallelToolCalls: true,\n signal\n })\n } catch (e: any) {\n // AbortError — 별도 분류\n if (e?.name === 'AbortError' || signal?.aborted) {\n log('iter %d: provider AbortError', iter)\n abortReason = { type: 'aborted', iter }\n } else {\n log('iter %d: provider error: %s', iter, e?.message)\n abortReason = {\n type: 'provider_error',\n message: e?.message ?? String(e),\n iter\n }\n }\n break\n }\n\n lastText = result.text || lastText\n stopReason = result.stopReason\n\n log(\n 'iter %d: text=%s, toolCalls=%d, stopReason=%s',\n iter,\n result.text ? `\"${result.text.slice(0, 60)}...\"` : '(none)',\n result.toolCalls.length,\n result.stopReason\n )\n\n if (result.toolCalls.length === 0) break\n\n // (1b) 삭제: chat() 직후 abort 는 다음 iter 시작 시 (1) 에서 감지.\n // 현재 iter tool 실행을 중단하면 read tool 결과도 버려져 테스트/동작 불일치.\n\n // tool 결과 빌드 — read 는 즉시 실행, write 는 누적, action 은 별도 채널\n const beforeUsages = toolUsages.length\n const toolResultParts = await processToolCalls(\n result.toolCalls,\n currentBoard,\n selectedRefids,\n dispatch,\n accumulatedWriteCalls,\n accumulatedActions,\n toolUsages\n )\n\n // (4) 같은 tool 의 validation 실패 연속 감지 — LLM 이 같은 잘못 반복 시 abort\n const newUsages = toolUsages.slice(beforeUsages)\n const repeatedFailureAbort = detectRepeatedValidationFailure(\n newUsages,\n lastFailedToolName,\n consecutiveFailures,\n failureLimit\n )\n if (repeatedFailureAbort.shouldAbort) {\n log(\n 'iter %d: repeated validation failure on %s (%d consecutive) — abort',\n iter,\n repeatedFailureAbort.toolName,\n repeatedFailureAbort.consecutive\n )\n abortReason = {\n type: 'repeated_validation_failure',\n toolName: repeatedFailureAbort.toolName,\n consecutive: repeatedFailureAbort.consecutive,\n iter\n }\n break\n }\n lastFailedToolName = repeatedFailureAbort.lastFailedToolName\n consecutiveFailures = repeatedFailureAbort.consecutive\n\n // assistant 의 tool_use turn + user 의 tool_result turn 을 conversation 에 누적\n const assistantContent: any[] = []\n if (result.text) assistantContent.push({ type: 'text', text: result.text })\n for (const tc of result.toolCalls) {\n // providerMeta (Gemini thoughtSignature 등) 를 그대로 round-trip — 안 보내면 400.\n assistantContent.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.name,\n arguments: tc.arguments,\n ...(tc.providerMeta && { providerMeta: tc.providerMeta })\n })\n }\n\n conversation = [\n ...conversation,\n { role: 'assistant', content: assistantContent },\n { role: 'user', content: toolResultParts }\n ]\n }\n\n // (3) max iterations 도달 시 사용자에게 표면화\n if (iter >= maxIterations && !abortReason) {\n log('iter %d: max iterations reached', iter)\n abortReason = { type: 'max_iterations', iter }\n }\n\n return {\n lastText,\n accumulatedWriteCalls,\n accumulatedActions,\n toolUsages,\n stopReason,\n abortReason\n }\n}\n\n/**\n * 같은 tool 의 validation 실패 연속 감지 — pure helper.\n *\n * 같은 tool 이 반복 fail 하면 LLM 이 self-correct 못 하는 상태.\n * 다른 tool 이 fail 하면 시퀀스 새로 시작 (전혀 다른 패턴이라).\n * tool 이 succeed 하면 카운터 reset.\n */\nfunction detectRepeatedValidationFailure(\n newUsages: ToolUsage[],\n lastFailedToolName: string | undefined,\n consecutiveFailures: number,\n limit: number\n): {\n shouldAbort: boolean\n toolName: string\n consecutive: number\n lastFailedToolName: string | undefined\n} {\n // 이번 iteration 에서 실패한 write tool 들 (validation 거절)\n const failed = newUsages.filter(\n u => u.kind === 'write' && (u.result as any)?.error === 'Tool args failed validation'\n )\n\n if (failed.length === 0) {\n // 실패 없음 — reset\n return {\n shouldAbort: false,\n toolName: '',\n consecutive: 0,\n lastFailedToolName: undefined\n }\n }\n\n // 모든 실패가 같은 tool 인지 확인\n const firstName = failed[0].name\n const allSame = failed.every(u => u.name === firstName)\n\n if (allSame && firstName === lastFailedToolName) {\n const consecutive = consecutiveFailures + 1\n return {\n shouldAbort: consecutive >= limit,\n toolName: firstName,\n consecutive,\n lastFailedToolName: firstName\n }\n }\n\n // 다른 tool 또는 첫 실패\n return {\n shouldAbort: false,\n toolName: firstName,\n consecutive: 1,\n lastFailedToolName: firstName\n }\n}\n\n/**\n * 한 iteration 의 tool 결과를 분류·실행·누적. (loop 에서 분리해 읽기 쉽게.)\n */\nasync function processToolCalls(\n toolCalls: AIToolCall[],\n currentBoard: BoardModel | undefined,\n selectedRefids: number[],\n dispatch: ToolDispatchHelpers,\n accumulatedWriteCalls: AIToolCall[],\n accumulatedActions: BoardActionOp[],\n toolUsages: ToolUsage[]\n): Promise<any[]> {\n const toolResultParts: any[] = []\n\n for (const tc of toolCalls) {\n if (dispatch.isReadTool(tc.name)) {\n const value = dispatch.executeReadTool(tc, currentBoard, selectedRefids)\n toolResultParts.push({\n type: 'tool_result',\n toolUseId: tc.id,\n content: JSON.stringify(value)\n })\n toolUsages.push({\n name: tc.name,\n arguments: tc.arguments ?? {},\n result: dispatch.summarizeToolResult(value),\n kind: 'read'\n })\n } else if (dispatch.isWriteTool(tc.name)) {\n // 사전 검증 — args 가 schema / build 함수 검증 통과해야 누적.\n // 실패 시 LLM 한테 detailed error 반환 → 다음 turn 에서 자기 회복 (retry).\n const components = (currentBoard?.components ?? []) as any[]\n const validation = dispatch.validateWriteToolCall(tc, components)\n if (!validation.valid) {\n const errorResult = {\n error: 'Tool args failed validation',\n issues: validation.errors,\n suggestion: validation.suggestion,\n note: 'Fix the args and call the tool again.'\n }\n toolResultParts.push({\n type: 'tool_result',\n toolUseId: tc.id,\n content: JSON.stringify(errorResult)\n })\n toolUsages.push({\n name: tc.name,\n arguments: tc.arguments ?? {},\n result: errorResult,\n kind: 'write'\n })\n // continue — 누적 안 함. LLM 이 다음 turn 에서 정정 호출.\n } else {\n accumulatedWriteCalls.push(tc)\n const queuedResult = { queued: true, note: 'Will be applied on the client.' }\n toolResultParts.push({\n type: 'tool_result',\n toolUseId: tc.id,\n content: JSON.stringify(queuedResult)\n })\n toolUsages.push({\n name: tc.name,\n arguments: tc.arguments ?? {},\n result: queuedResult,\n kind: 'write'\n })\n }\n } else if (dispatch.isActionTool(tc.name)) {\n // ephemeral scene 조작 — accumulatedActions 로 누적, ChatResponse.actions 로 client 전달\n const action = dispatch.toolCallToBoardActionOp(tc)\n if (action) accumulatedActions.push(action)\n const queuedResult = {\n queued: true,\n note: 'Will be executed on the modeller (ephemeral).'\n }\n toolResultParts.push({\n type: 'tool_result',\n toolUseId: tc.id,\n content: JSON.stringify(queuedResult)\n })\n toolUsages.push({\n name: tc.name,\n arguments: tc.arguments ?? {},\n result: queuedResult,\n kind: 'write'\n })\n } else {\n const errResult = { error: `Unknown tool: ${tc.name}` }\n toolResultParts.push({\n type: 'tool_result',\n toolUseId: tc.id,\n content: JSON.stringify(errResult),\n isError: true\n })\n toolUsages.push({\n name: tc.name,\n arguments: tc.arguments ?? {},\n result: errResult,\n kind: 'unknown'\n })\n }\n }\n\n return toolResultParts\n}\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoardEditPatch 를 BoardModel 에 적용하는 표준 helper.
|
|
3
|
+
*
|
|
4
|
+
* 호출자가 patch 응답을 받아 보드에 반영할 때 사용.
|
|
5
|
+
* Pure function — 입력을 mutate 하지 않음.
|
|
6
|
+
*/
|
|
7
|
+
import type { BoardComponent, BoardModel } from '@things-factory/board-import';
|
|
8
|
+
import type { BoardEditOp, BoardEditPatch } from './types.js';
|
|
9
|
+
export interface PatchApplyReport {
|
|
10
|
+
/** 패치 적용 후 보드. 모든 op 가 noop 이어도 입력 그대로 반환. */
|
|
11
|
+
board: BoardModel;
|
|
12
|
+
/** 실제로 보드를 바꾼 op 들. */
|
|
13
|
+
applied: BoardEditOp[];
|
|
14
|
+
/** id 매칭 실패 등으로 noop 이 된 op 들 — 호출자가 사용자에게 알릴 단서. */
|
|
15
|
+
missed: BoardEditOp[];
|
|
16
|
+
}
|
|
17
|
+
export declare function applyBoardEditPatch(board: BoardModel | undefined, patch: BoardEditPatch): BoardModel;
|
|
18
|
+
/**
|
|
19
|
+
* Verbose 변형 — 각 op 의 적용 여부를 보고.
|
|
20
|
+
*
|
|
21
|
+
* `modify` 와 `remove` 는 id 가 보드에 없으면 silent no-op 이 된다 (patch 함수의
|
|
22
|
+
* 의도적 단순화). LLM 이 잘못된 id 를 만들어 보내면 사용자에게 "수정했습니다"
|
|
23
|
+
* 라고 답하지만 실제로는 아무 변화도 없는 상황이 발생 — 호스트가 missed 를
|
|
24
|
+
* 보고 사용자에게 경고할 수 있도록 별도 entry point 제공.
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyBoardEditPatchVerbose(board: BoardModel | undefined, patch: BoardEditPatch): PatchApplyReport;
|
|
27
|
+
/**
|
|
28
|
+
* 주어진 board 상태에서 op 의 inverse 를 계산.
|
|
29
|
+
*
|
|
30
|
+
* Revert 기능의 코어 — patch 적용 직전 board 와 op 만 알면 그 op 의 역연산을
|
|
31
|
+
* 만들 수 있으므로, 호스트가 in-place 적용하면서 함께 누적해 두면 나중에
|
|
32
|
+
* 역순 실행만으로 복원 가능.
|
|
33
|
+
*
|
|
34
|
+
* 반환:
|
|
35
|
+
* - inverse op 또는 null (unsupported / 데이터 부족)
|
|
36
|
+
* - add 의 inverse 는 add 후 발급되는 refid 가 필요해서 모델 단계에서 계산 불가 →
|
|
37
|
+
* 호스트가 scene.add 직후 refid 를 캡처해 직접 만들 것 (이 함수는 pre-applied
|
|
38
|
+
* board 만 보고 만들 수 있는 종류만 처리: remove / modify / modifyBoard / replace)
|
|
39
|
+
*/
|
|
40
|
+
export declare function computeInverseOp(board: BoardModel | undefined, op: BoardEditOp): BoardEditOp | null;
|
|
41
|
+
export declare function applyOp(board: BoardModel, op: BoardEditOp): BoardModel;
|
|
42
|
+
/**
|
|
43
|
+
* 컴포넌트에 부분 patch 를 적용.
|
|
44
|
+
* threeD 등 nested object 는 deep merge — 호출자가 색만 바꾸려고 했을 때 geometry 까지 사라지지 않도록.
|
|
45
|
+
* patchVal === null 이면 해당 키를 제거한다 (위 시맨틱 참조).
|
|
46
|
+
*/
|
|
47
|
+
export declare function mergeComponent(base: BoardComponent, patch: Partial<BoardComponent>): BoardComponent;
|