@things-factory/board-ai 10.0.18 → 10.1.0

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.
@@ -4,7 +4,7 @@
4
4
  import type { AIClient, AITool, AIToolCall, AIToolChatResult } from '@things-factory/ai-client-base';
5
5
  import { type BoardModel, type ComponentCategory } from '@things-factory/board-import';
6
6
  import type { BoardAIAssistant, BoardEditOp, ChatOptions, ChatResponse, LLMMessage } from './types.js';
7
- import { type AgenticLoopResult } from './agentic-loop';
7
+ import { type AgenticLoopResult } from '@things-factory/ai-client-base';
8
8
  import { type ToolSpec, type ToolCategoryFilter } from '@things-factory/ai-client-base';
9
9
  export interface BoardAIAssistantOptions {
10
10
  /** 기본 scope (chat 호출 시 옵션이 없으면 사용) */
@@ -22,11 +22,18 @@ const catalog_resolver_1 = require("./catalog-resolver");
22
22
  // styling write tool builder 들은 registry (./styling/registry.ts) 에서 등록 + dispatch.
23
23
  // 직접 import 안 함 — 신규 styling tool 추가 시 registry 에만 spec 추가하면 됨.
24
24
  const read_tools_1 = require("./styling/read-tools");
25
- const agentic_loop_1 = require("./agentic-loop");
25
+ /*
26
+ * 도구 루프는 **계약 층이 소유한다**(2026-09-05). 전에는 이 패키지 안에 있었고, twin-ai 에 같은
27
+ * 모양이 한 벌 더 있었다 — 그래서 안전 장치가 서로 반대로 갈렸다(여기는 중단·조기종료, 저쪽은
28
+ * 접지 가드). 합친 것이 `@things-factory/ai-client-base` 에 있다.
29
+ *
30
+ * board 의 것(문서·선택 refid·편집 op)은 타입 매개변수로 넘긴다 — 루프는 그 모양을 모른다.
31
+ */
32
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
26
33
  const grounding_1 = require("./grounding");
27
34
  const component_tree_1 = require("./component-tree");
28
35
  const registry_js_1 = require("./styling/registry.js");
29
- const ai_client_base_1 = require("@things-factory/ai-client-base");
36
+ const ai_client_base_2 = require("@things-factory/ai-client-base");
30
37
  const tool_validation_1 = require("./validation/tool-validation");
31
38
  const ALL_CATEGORIES = [
32
39
  'container',
@@ -102,7 +109,7 @@ class DefaultBoardAIAssistant {
102
109
  * 실제로 그 공백에서 사고가 났다 — 조치 요청에 제안 도구를 부르지 않고 "제안해 두었습니다" 라고만
103
110
  * 답해, 사용자에게 없는 버튼을 누르라고 안내했다. 규율은 도구를 등록한 쪽이 소유하고 여기서는
104
111
  * 모아 붙이기만 한다(도메인 지식이 board-ai 프롬프트로 새지 않는다). */
105
- const guidance = (0, ai_client_base_1.getCategoryGuidance)(options.toolCategories);
112
+ const guidance = (0, ai_client_base_2.getCategoryGuidance)(options.toolCategories);
106
113
  const systemPrompt = this.buildSystemPromptForTools(knownTypes, categories, schemas) +
107
114
  (guidance.length ? `\n\n==== TOOL USE RULES (from the tools exposed to this conversation) ====\n${guidance.join('\n')}` : '');
108
115
  const selectedRefids = (options.selectedRefids ?? []).filter((n) => typeof n === 'number' && Number.isFinite(n));
@@ -156,7 +163,7 @@ class DefaultBoardAIAssistant {
156
163
  // 3) 미등록 / 잘못된 분기 — 빈 응답. agentic-loop 가 LLM 에 회신 후 다음 turn.
157
164
  return undefined;
158
165
  };
159
- const loopResult = await (0, agentic_loop_1.runAgenticLoop)({
166
+ const loopResult = await (0, ai_client_base_1.runAgenticLoop)({
160
167
  initialConversation: conversation,
161
168
  tools,
162
169
  options: {
@@ -173,13 +180,13 @@ class DefaultBoardAIAssistant {
173
180
  isReadTool: gate.isRead,
174
181
  isWriteTool: gate.isWrite,
175
182
  isActionTool: gate.isAction,
176
- executeReadTool: wrappedExecuteReadTool,
177
- validateWriteToolCall: tool_validation_1.validateWriteToolCall,
178
- toolCallToBoardActionOp,
183
+ executeReadTool: (tc, ctx) => wrappedExecuteReadTool(tc, ctx.board, ctx.selectedRefids),
184
+ /* 문서에서 부품을 꺼내는 일은 **여기서** 한다 — 루프는 board 의 모양을 모른다. */
185
+ validateWriteToolCall: (tc, ctx) => (0, tool_validation_1.validateWriteToolCall)(tc, (ctx.board?.components ?? [])),
186
+ toolCallToOp: toolCallToBoardActionOp,
179
187
  summarizeToolResult
180
188
  },
181
- currentBoard,
182
- selectedRefids
189
+ context: { board: currentBoard, selectedRefids }
183
190
  });
184
191
  const accumulatedWriteCalls = loopResult.accumulatedWriteCalls;
185
192
  const accumulatedActions = loopResult.accumulatedActions;
@@ -1012,7 +1019,7 @@ function buildBoardEditTools(knownTypes, exposure = {}) {
1012
1019
  /* 코어(보드 편집) 도구를 끈 대화는 **외부 카테고리 도구만** 남긴다.
1013
1020
  * 배열 리터럴 안 어디에 있든 이름 기준으로 걸러 누락이 없게 한다. */
1014
1021
  if (!gate.coreOn) {
1015
- const externalNames = new Set((0, ai_client_base_1.getToolSpecsFor)(gate.filter).map(spec => spec.name));
1022
+ const externalNames = new Set((0, ai_client_base_2.getToolSpecsFor)(gate.filter).map(spec => spec.name));
1016
1023
  return tools.filter(tool => externalNames.has(tool.name));
1017
1024
  }
1018
1025
  return tools;
@@ -1788,7 +1795,7 @@ function isReadTool(name) {
1788
1795
  // tool-registry plugin point — 'read' 와 'external' 는 LLM 측에서 동일하게 다뤄짐
1789
1796
  // (server 즉시 실행 + 결과 회신). external 은 board model 외부 entity 변경이
1790
1797
  // 가능한 점에서 의미 차이가 있지만, dispatch path 는 read 와 같다.
1791
- const kind = (0, ai_client_base_1.getToolKind)(name);
1798
+ const kind = (0, ai_client_base_2.getToolKind)(name);
1792
1799
  return kind === 'read' || kind === 'external';
1793
1800
  }
1794
1801
  /**
@@ -1820,7 +1827,7 @@ function formatAbortNotice(abortReason) {
1820
1827
  /* 남의 말(SDK 원문)을 사용자에게 보여주지 않는다 — "Error fetching from undefined: [503 …]" 은
1821
1828
  * 사용자가 할 수 있는 일을 알려주지 않는다. 일시 장애는 이미 몇 번 다시 시도한 뒤이므로,
1822
1829
  * 지금 필요한 안내는 "잠시 후 다시" 하나다. 원문은 서버 로그에 남는다. */
1823
- return (0, ai_client_base_1.isTransientAIError)({ message: abortReason.message })
1830
+ return (0, ai_client_base_2.isTransientAIError)({ message: abortReason.message })
1824
1831
  ? '\n\n_(AI 서비스가 일시적으로 응답하지 않았어요. 잠시 후 다시 시도해 주세요.)_'
1825
1832
  : '\n\n_(처리 중 오류가 발생했어요. 같은 요청이 계속 실패하면 관리자에게 알려 주세요.)_';
1826
1833
  case 'max_iterations':
@@ -1867,24 +1874,24 @@ function summarizeToolResult(value, depth = 0) {
1867
1874
  function isWriteTool(name) {
1868
1875
  if (WRITE_TOOL_NAMES.has(name))
1869
1876
  return true;
1870
- return (0, ai_client_base_1.getToolKind)(name) === 'write';
1877
+ return (0, ai_client_base_2.getToolKind)(name) === 'write';
1871
1878
  }
1872
1879
  function isActionTool(name) {
1873
1880
  if (ACTION_TOOL_NAMES.has(name))
1874
1881
  return true;
1875
- return (0, ai_client_base_1.getToolKind)(name) === 'action';
1882
+ return (0, ai_client_base_2.getToolKind)(name) === 'action';
1876
1883
  }
1877
1884
  function buildToolGate(exposure = {}) {
1878
1885
  const coreOn = exposure.boardTools !== false;
1879
1886
  const filter = exposure.toolCategories;
1880
- const kind = (name) => (0, ai_client_base_1.getToolKind)(name, filter);
1887
+ const kind = (name) => (0, ai_client_base_2.getToolKind)(name, filter);
1881
1888
  return {
1882
1889
  coreOn,
1883
1890
  filter,
1884
1891
  isRead: name => (coreOn && READ_TOOL_NAMES.has(name)) || kind(name) === 'read' || kind(name) === 'external',
1885
1892
  isWrite: name => (coreOn && WRITE_TOOL_NAMES.has(name)) || kind(name) === 'write',
1886
1893
  isAction: name => (coreOn && ACTION_TOOL_NAMES.has(name)) || kind(name) === 'action',
1887
- spec: name => (0, ai_client_base_1.findToolSpec)(name, filter)
1894
+ spec: name => (0, ai_client_base_2.findToolSpec)(name, filter)
1888
1895
  };
1889
1896
  }
1890
1897
  /**
@@ -1893,7 +1900,7 @@ function buildToolGate(exposure = {}) {
1893
1900
  * (board-import 등) 도 본 함수로 합집합. buildBoardEditTools 의 spread 부분에서 호출.
1894
1901
  */
1895
1902
  function buildExternalCategoryDefinitions(filter) {
1896
- return (0, ai_client_base_1.getToolSpecsFor)(filter).map(spec => ({
1903
+ return (0, ai_client_base_2.getToolSpecsFor)(filter).map(spec => ({
1897
1904
  name: spec.name,
1898
1905
  description: spec.themeNote ? `${spec.description} ${spec.themeNote}` : spec.description,
1899
1906
  parameters: spec.schema