@things-factory/figure-service 10.1.64 → 10.1.66

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.
Files changed (32) hide show
  1. package/dist-server/service/figure/figure-mutation.d.ts +4 -8
  2. package/dist-server/service/figure/figure-mutation.js +13 -27
  3. package/dist-server/service/figure/figure-mutation.js.map +1 -1
  4. package/dist-server/service/figure/figure-propose-type.d.ts +8 -59
  5. package/dist-server/service/figure/figure-propose-type.js +15 -150
  6. package/dist-server/service/figure/figure-propose-type.js.map +1 -1
  7. package/dist-server/service/figure/figure-propose-v3.d.ts +50 -0
  8. package/dist-server/service/figure/figure-propose-v3.js +143 -0
  9. package/dist-server/service/figure/figure-propose-v3.js.map +1 -0
  10. package/dist-server/service/figure/figure-tools.js +64 -90
  11. package/dist-server/service/figure/figure-tools.js.map +1 -1
  12. package/dist-server/tsconfig.tsbuildinfo +1 -1
  13. package/package.json +3 -3
  14. package/server/service/figure/figure-llm-smoke.test.ts +28 -32
  15. package/server/service/figure/figure-mutation.ts +14 -28
  16. package/server/service/figure/figure-propose-type.ts +16 -124
  17. package/server/service/figure/figure-propose-v3.test.ts +117 -0
  18. package/server/service/figure/figure-propose-v3.ts +174 -0
  19. package/server/service/figure/figure-tools.test.ts +86 -133
  20. package/server/service/figure/figure-tools.ts +60 -91
  21. package/dist-server/service/figure/figure-propose.d.ts +0 -91
  22. package/dist-server/service/figure/figure-propose.js +0 -439
  23. package/dist-server/service/figure/figure-propose.js.map +0 -1
  24. package/dist-server/service/figure/figure-quality.d.ts +0 -29
  25. package/dist-server/service/figure/figure-quality.js +0 -62
  26. package/dist-server/service/figure/figure-quality.js.map +0 -1
  27. package/server/service/figure/figure-ai-flow.test.ts +0 -93
  28. package/server/service/figure/figure-e2e-smoke.test.ts +0 -72
  29. package/server/service/figure/figure-propose.test.ts +0 -719
  30. package/server/service/figure/figure-propose.ts +0 -551
  31. package/server/service/figure/figure-quality.test.ts +0 -43
  32. package/server/service/figure/figure-quality.ts +0 -98
@@ -6,12 +6,12 @@
6
6
  * 하려면 어시스턴트가 **지금 무엇이 있는지 읽을 수 있어야** 한다. 읽지 못하면 매번 사람이 정본을
7
7
  * 붙여 넣어야 하고, 그러면 대화가 아니다.
8
8
  *
9
- * 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigure` ·
9
+ * 능력을 복제하지 않는다. figure-service 가 이미 가진 진입점(`inspectSource` · `proposeFigureV3` ·
10
10
  * 조회)을 레지스트리에 투영한다.
11
11
  *
12
12
  * ── 전부 조회·제안 전용이다 ─────────────────────────────────────────────────
13
13
  * 정본을 고치는 도구를 넣지 않는다. 형식의 설계가 그렇게 정해져 있다 — 어시스턴트는 **후보**를
14
- * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose.ts`). 되돌릴 수 없는 조작이 대화면으로
14
+ * 내고, 받을지 버릴지는 저작자가 정한다(`figure-propose-v3.ts`, ADR-0094). 되돌릴 수 없는 조작이 대화면으로
15
15
  * 새지 않으므로 전부 `kind: 'read'` 다.
16
16
  *
17
17
  * 저장은 저작면이 `updateFigure` 로 한다. 그 mutation 은 `figure:mutation` 이 지키고, 대화는
@@ -53,7 +53,7 @@ function getDeps(): any {
53
53
  // eslint-disable-next-line @typescript-eslint/no-var-requires
54
54
  const format = require('./figure-format.js')
55
55
  // eslint-disable-next-line @typescript-eslint/no-var-requires
56
- const propose = require('./figure-propose.js')
56
+ const proposeV3 = require('./figure-propose-v3.js')
57
57
  _deps = {
58
58
  getRepository: shell.getRepository,
59
59
  Figure: entity.Figure,
@@ -65,7 +65,7 @@ function getDeps(): any {
65
65
  parseAnySource: format.parseAnySource,
66
66
  v3CostOf: model.v3CostOf,
67
67
  v3ScoreOf: model.v3ScoreOf,
68
- proposeFigure: propose.proposeFigure,
68
+ proposeFigureV3: proposeV3.proposeFigureV3,
69
69
  compile: model.compile,
70
70
  costOf: model.costOf,
71
71
  scoreOf: model.scoreOf,
@@ -160,16 +160,15 @@ async function resolveFigureTarget(figureId: string | undefined, domainId: strin
160
160
  const isV3 = (source: any) => source?.version === 'figure-v3-asset-1'
161
161
 
162
162
  /**
163
- * The V2 proposer writes V2 figures; a V3 figure open in the modeller is not something it can revise or add to. Said as
164
- * what is true, with what the assistant can do instead -- not a V2 parser's complaint about a V3 asset (user,
165
- * 2026-09-24: the dock answered 「type 이 없다 · base 가 없다」 for a V3 draft).
163
+ * A revision is applied by the modeller on the version the model saw (ADR-0094), so only the figure open there can be
164
+ * revised. Said as what is true, with what the assistant can do instead.
166
165
  */
167
- function v3ProposalUnavailable() {
166
+ function figureNotOpen() {
168
167
  return {
169
- error: 'V3_PROPOSAL_UNAVAILABLE',
170
- message: '이 도형은 V3 형식입니다. AI 후보 제안은 아직 V2 도형만 만들 수 있어 이 도형을 고치는 후보를 낼 수 없습니다.',
168
+ error: 'FIGURE_NOT_OPEN',
169
+ message: '고치는 제안은 모델러에 열려 있는 도형에만 낼 수 있습니다.',
171
170
  nextStep:
172
- 'Tell the person plainly that candidate proposals do not support V3 figures yet. You can still read the figure (getFigureSource) and explain whether it may be released and why (inspectFigure). Do not produce a V2 candidate for a V3 figure.'
171
+ 'Tell the person to open the figure in the modeller and ask again there. A new figure needs no open one: call proposeFigureCandidate with create: true.'
173
172
  }
174
173
  }
175
174
 
@@ -221,23 +220,6 @@ function sourceForTool(row: any, host: any): { source: any; draft: boolean } {
221
220
  return { source: normalizedDraft, draft: true }
222
221
  }
223
222
 
224
- /**
225
- * 후보를 냈던 바로 그 Figure의 세션 반응만 후보 생성기에 넘긴다.
226
- *
227
- * 도크의 host context는 브라우저 값이므로 이 함수는 접근 권한을 판단하지 않는다. 이미 현재
228
- * 도메인에서 Figure를 찾은 뒤, `proposeFigure`가 다시 길이·형식을 정리할 참고 데이터만 건넨다.
229
- */
230
- function feedbackForTool(host: any, figureId?: string): unknown[] | undefined {
231
- if (!Array.isArray(host?.proposalFeedback)) return undefined
232
- if (figureId && host?.figureId === figureId) {
233
- return host.proposalFeedback.slice(-5)
234
- }
235
- if (host?.editorMode === 'new' || !host?.figureId) {
236
- return host.proposalFeedback.slice(-5)
237
- }
238
- return undefined
239
- }
240
-
241
223
  /**
242
224
  * 무엇을 고치라는 것인지 대화면 문맥에서 해소한다.
243
225
  *
@@ -395,9 +377,9 @@ function figureToolSpecs(): ToolSpec[] {
395
377
  /* 후보 생성 — 저장은 하지 않지만 같은 문의 권한을 쓴다. */
396
378
  doors: ['Mutation.proposeFigure'],
397
379
  description:
398
- 'Asks for a figure candidate from a description, optionally revising an existing one. ' +
399
- 'Returns the candidate with its score and cost. NOTHING IS SAVED — the author accepts or ' +
400
- 'discards it on screen. This is the only way a candidate reaches the screen.',
380
+ 'Asks for a proposal: the modeller commands that make a new figure or revise the one open in the modeller. ' +
381
+ 'Returns the commands with the score and cost of the figure they make. NOTHING IS SAVED — the author applies ' +
382
+ 'the proposal in the modeller as one step, or leaves it. This is the only way a proposal reaches the screen.',
401
383
  schema: {
402
384
  type: 'object',
403
385
  properties: {
@@ -407,11 +389,11 @@ function figureToolSpecs(): ToolSpec[] {
407
389
  },
408
390
  figureId: {
409
391
  type: 'string',
410
- description: 'Revise this figure. Omit to revise the one open in the editor; pass null to create anew.'
392
+ description: 'Revise this figure; it must be the one open in the modeller. Omit to revise the open one; pass null to create anew.'
411
393
  },
412
- type: {
394
+ name: {
413
395
  type: 'string',
414
- description: 'Type name for a new figure. Check listFigures first — it is a stored identifier.'
396
+ description: 'The name a person reads for a new figure.'
415
397
  },
416
398
  create: {
417
399
  type: 'boolean',
@@ -429,54 +411,52 @@ function figureToolSpecs(): ToolSpec[] {
429
411
  고치는 것인지 만드는 것인지를 **문맥에서 정한다.** 열린 도형이 있으면 고치는 것이 기본이고,
430
412
  모델이 `create` 를 켜면 만드는 것이다. 이것을 모델의 짐작에 맡기면 열려 있는 도형을 덮어쓰는
431
413
  후보가 온다.
414
+
415
+ 고치는 제안은 모델러가 **요청 당시의 판(context · revision)** 위에 한 단계로 적용한다(ADR-0094).
416
+ 그래서 고칠 수 있는 것은 모델러에 열린 도형뿐이고, 그 판을 문맥이 함께 실어 와야 한다.
432
417
  */
418
+ const host = ctx?.host
419
+ const create = args?.create === true || args?.figureId === null
433
420
  let base: unknown
434
- if (!args?.create && args?.figureId !== null) {
435
- const open = typeof args?.figureId === 'string' ? args.figureId : ctx?.host?.figureId
436
- const row = await resolveFigureTarget(open ? open.trim() : undefined, domainId, ctx?.host)
437
- if (row) {
438
- base = sourceForTool(row, ctx?.host).source ?? undefined
421
+ let figureId: string | undefined
422
+ if (!create) {
423
+ const said = typeof args?.figureId === 'string' ? args.figureId.trim() : ''
424
+ const open = typeof host?.figureId === 'string' ? host.figureId.trim() : ''
425
+ if (said && said !== open) {
426
+ await figureInDomain(said, domainId)
427
+ return figureNotOpen()
439
428
  }
440
- }
441
- if (isV3(base) || (args?.create !== true && isV3(ctx?.host?.draftSource))) return v3ProposalUnavailable()
442
-
443
- const palette = Array.isArray(ctx?.host?.palette) ? ctx.host.palette : []
444
- if (palette.length === 0) {
445
- /*
446
- 팔레트를 모르면 모델이 색을 지어내고, 그 후보는 그리는 시점에 `resolveToken` 이 던진다 —
447
- 저장까지 되고 나서 화면에서 죽는다. 빈 목록으로 진행하지 않는다.
448
- */
449
- throw new Error('the palette is missing from the host context — the candidate would invent colours')
429
+ if (!open) return missingFigureTarget()
430
+ const row = await resolveFigureTarget(open, domainId, host)
431
+ if (!row) return missingFigureTarget()
432
+ base = sourceForTool(row, host).source ?? undefined
433
+ if (!isV3(base)) return figureNotOpen()
434
+ if (typeof host?.context !== 'string' || !Number.isInteger(host?.revision)) return figureNotOpen()
435
+ figureId = open
450
436
  }
451
437
 
452
- const { proposeFigure } = getDeps()
453
- const feedback = feedbackForTool(ctx?.host, base ? figureIdFrom(args, ctx?.host) : undefined)
454
-
455
438
  let hostImage: { data: Buffer; mediaType: any } | undefined
456
- const rawImage = ctx?.host?.image || (Array.isArray(ctx?.host?.attachments) ? ctx.host.attachments.find((a: any) => a?.mediaType?.startsWith('image/')) : undefined)
439
+ const rawImage = host?.image || (Array.isArray(host?.attachments) ? host.attachments.find((a: any) => a?.mediaType?.startsWith('image/')) : undefined)
457
440
  if (rawImage?.data) {
458
441
  const rawData = typeof rawImage.data === 'string' ? rawImage.data.replace(/^data:image\/[a-zA-Z]+;base64,/, '') : rawImage.data
459
442
  const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)
460
443
  hostImage = { data: buf, mediaType: rawImage.mediaType || 'image/png' }
461
444
  }
462
445
 
446
+ const { proposeFigureV3 } = getDeps()
463
447
  let result: any
464
448
  try {
465
- result = await proposeFigure({
449
+ result = await proposeFigureV3({
466
450
  prompt,
467
- palette,
468
451
  base,
469
- type: typeof args?.type === 'string' ? args.type : undefined,
470
- feedback,
471
- refine: !!feedback?.length,
452
+ name: typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : undefined,
472
453
  image: hostImage
473
454
  })
474
455
  } catch (error) {
475
456
  /*
476
- * 후보 생성은 세 번의 JSON·형식 검사 끝에 실패할 수 있다. 그 마지막 검사 사유를
477
- * ProposeFailure.reasons 에만 두면 agentic-loop은 "만들지 못했습니다"만 받아
478
- * 사용자 화면에는 "응답이 비어있습니다"가 된다. 재시도할 수 있는 말로 경계를
479
- * 넘긴다. 다른 예외(권한·통신)는 원문을 보존한다.
457
+ * 세 번 거절된 끝의 사유를 ProposeV3Failure.reasons 에만 두면 agentic-loop 은 「만들지 못했습니다」만
458
+ * 받아 화면에는 「응답이 비어있습니다」가 된다. 다시 시도할 수 있는 말로 경계를 넘긴다.
459
+ * 다른 예외(권한·통신)는 원문을 보존한다.
480
460
  */
481
461
  const reasons = Array.isArray((error as any)?.reasons) ? (error as any).reasons : []
482
462
  if (reasons.length > 0) throw new Error([(error as Error).message, ...reasons].join('\n'))
@@ -484,37 +464,26 @@ function figureToolSpecs(): ToolSpec[] {
484
464
  }
485
465
 
486
466
  /*
487
- **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 후보는 도구 추적 안에 묻히고,
488
- 화면에는 아무것도 생기지 않는다 — 그러면 모델이 「후보를 만들었습니다」라고 쓴 것이
489
- 거짓말이 된다(사용자가 없는 단추를 찾는다).
490
-
491
- `label`·`reason` 이 카드에 그려지는 두 줄이고, `args` 는 같은 제안을 두 번 세지 않기
492
- 위한 열쇠의 일부다 — 같은 요청을 두 번 내면 카드가 하나로 접힌다.
467
+ **`proposed: true` 가 카드를 만든다.** 이 표시가 없으면 제안은 도구 추적 안에 묻히고 화면에는
468
+ 아무것도 생기지 않는다. 카드의 실행 단추가 figure-ui 의 처리기에 넘기고, 처리기가 모델러에 명령
469
+ 목록을 한 단계로 적용한다. `args` 는 같은 제안을 두 번 세지 않기 위한 열쇠의 일부다.
493
470
  */
471
+ const name = result.source.name || 'Figure'
494
472
  return {
495
473
  proposed: true,
496
- label: base ? `${result.source.type} 고치기` : `${result.source.type} 만들기`,
474
+ icon: 'view_in_ar',
475
+ label: create ? `${name} 만들기` : `${name} 고치기`,
497
476
  reason:
498
- `등급 ${result.score.grade} · 삼각형 ${result.cost.triangles} · ` +
499
- `한 도면 ${result.cost.at.drawCalls}회 · ${result.attempts}번에 만들었다`,
500
- choices: [
501
- { id: 'save', action: 'save', label: '초안 저장', icon: 'save', primary: true },
502
- { id: 'repropose', action: 'prompt', label: '다른 콘셉트 재제안', icon: 'refresh', prompt: '현재 제안과 완전히 다른 스타일과 콘셉트로 새로 제안해줘' },
503
- { id: 'slim', action: 'prompt', label: '비율 슬림 조정', icon: 'aspect_ratio', prompt: '전체적인 가로세로 비율을 좀 더 슬림하고 날렵하게 조정해줘' },
504
- { id: 'palette', action: 'prompt', label: '색상 테마 변경', icon: 'palette', prompt: '다른 팔레트 색상 테마로 부품 색상을 변경해줘' },
505
- { id: 'detail', action: 'prompt', label: '디테일 보강', icon: 'extension', prompt: '주요 디테일과 센서 부품을 좀 더 풍부하게 보강해줘' }
506
- ],
507
- args: {
508
- baseSource: base ? JSON.stringify(base) : undefined,
509
- prompt, figureId: base ? figureIdFrom(args, ctx?.host) : undefined,
510
- draftType: base && ctx?.host?.editorMode === 'new' && !figureIdFrom(args, ctx?.host)
511
- ? (base as any).type : undefined
512
- },
513
- source: result.source,
477
+ `명령 ${result.steps.length}개 · 등급 ${result.score?.grade ?? '-'} · 삼각형 ${result.cost.triangles} · ` +
478
+ `${result.attempts}번에 만들었다`,
479
+ args: { prompt, figureId, create },
480
+ create,
481
+ figureId,
482
+ context: create ? undefined : host.context,
483
+ revision: create ? undefined : host.revision,
484
+ steps: result.steps,
514
485
  score: result.score,
515
486
  cost: result.cost,
516
- quality: result.quality,
517
- violations: result.violations,
518
487
  attempts: result.attempts
519
488
  }
520
489
  }
@@ -537,13 +506,13 @@ export function registerFigureToolCategories(): void {
537
506
  '- **도형을 만들거나 고쳐 달라는 요청에는 반드시 `proposeFigureCandidate` 를 호출한다.**',
538
507
  ' 후보는 도구 호출 결과에서만 생긴다.',
539
508
  ' 호출 없이 "후보를 만들었습니다" · "적용 버튼을 누르세요" 라고 쓰는 것은 **거짓이다** — 화면에 후보 칸이 생기지 않으므로 사용자는 존재하지 않는 버튼을 찾는다.',
540
- ' 도크에서는 후보 카드의 버튼을 눌러야 모델러 비교 화면이 열린다. 아직 열지 않은 비교 화면이 이미 보인다고 말하지 않는다.',
541
- ' 요청한 부품만 바뀌었다고 추측하지 않는다. 후보와 원본을 비교한 근거가 없으면 변경 범위는 비교 화면에서 확인하도록 안내한다.',
509
+ ' 후보는 모델러 명령 목록이다. 도크 카드의 실행 단추를 눌러야 모델러에 한 단계로 적용되고, 되돌리기 한 번에 통째로 돌아간다. 적용 전에 이미 바뀌었다고 말하지 않는다.',
510
+ ' 고치는 제안은 모델러에 열린 도형에만 낼 수 있다. 결과의 `steps` 가 무엇을 하는지 그대로 전한다 — 명령에 없는 변경을 지어내지 않는다.',
542
511
  '- 정본을 직접 저장하지 않는다. 후보만 내고, 받을지 버릴지는 저작자가 화면에서 정한다.',
543
512
  '- 좌표와 크기는 X/Y/Z 축 이름으로 정확하게 표시한다. 확인되지 않은 가로·세로·높이 의미를 임의로 붙이지 않는다.',
544
513
  '- **고치라는 요청이면 먼저 `getFigureSource` 를 부른다.** 읽지 않고 고치면 짐작을 상대로 후보를 내게 되고, 부품 이름이 갈려 이미 배치된 보드의 바인딩이 끊긴다.',
545
- '- 목록 화면에는 열린 도형이 없다. 기존 도형을 조회·수정할 때는 `listFigures`에서 사용자가 말한 이름·타입의 id를 찾는다. 대상이 모호할 때만 어느 도형인지 묻는다. 새 제작 요청에는 기존 정본을 조회하지 않고 `proposeFigureCandidate`를 create: true로 호출한다.',
546
- '- 타입 이름을 지어내지 않는다 — `listFigures` 로 확인한다. **타입 이름은 저장되는 식별자**이고 보드가 그 이름으로 도형을 찾는다.',
514
+ '- 목록 화면에는 열린 도형이 없다. 기존 도형을 조회할 때는 `listFigures`에서 사용자가 말한 이름·타입의 id를 찾는다. 대상이 모호할 때만 어느 도형인지 묻는다. 기존 도형을 고치려면 모델러에서 그 도형을 열고 거기서 요청하도록 안내한다. 새 제작 요청에는 기존 정본을 조회하지 않고 `proposeFigureCandidate`를 create: true로 호출한다.',
515
+ '- 타입 이름을 지어내지 않는다 — `listFigures` 로 확인한다. **타입 이름은 저장되는 식별자**이고 보드가 그 이름으로 도형을 찾는다. 새 도형의 타입은 첫 저장 때 사람이 정한다.',
547
516
  '- 발행이 안 되는 이유를 물으면 `inspectFigure` 를 부른다. 판정 없이 "발행할 수 있습니다"라고 말하지 않는다.',
548
517
  ' 판정의 각 항목은 **왜 문제인지와 무엇을 바꾸면 되는지**를 함께 담고 있다. 그대로 전한다 — 요약하면 저작자가 어느 부품의 어느 축을 고칠지 다시 찾아야 한다.',
549
518
  '- 무게·삼각형 수를 물으면 `getFigureSource` 의 `cost` 를 쓴다. 눈대중으로 세지 않는다.'
@@ -1,91 +0,0 @@
1
- import { costOf, scoreOf } from '@hatiolab/figure-model';
2
- import type { FigureSource } from '@hatiolab/figure-model';
3
- import type { AIImageMediaType } from '@things-factory/ai-client-base';
4
- import type { FigureQualityReview } from './figure-quality.js';
5
- export interface ProposeInput {
6
- /** 사람이 시킨 말. */
7
- prompt: string;
8
- /** 고쳐 달라는 것이면 지금 정본. 없으면 새로 만든다. */
9
- base?: FigureSource;
10
- /** 참고 그림. 설비 사진·렌더. */
11
- image?: {
12
- data: Buffer;
13
- mediaType: AIImageMediaType;
14
- };
15
- /** 만들 타입 이름. 새로 만들 때 필요하다. */
16
- type?: string;
17
- /** 쓸 수 있는 팔레트 토큰. 여기 없는 토큰은 거절한다. */
18
- palette: string[];
19
- /** Measurable concerns warrant one more candidate attempt; never writes a Figure. */
20
- refine?: boolean;
21
- /** Explicit, session-only author reactions to earlier candidates; never persisted. */
22
- feedback?: unknown;
23
- }
24
- /**
25
- * 후보 하나에 대한 저작자의 명시적 반응.
26
- *
27
- * 브라우저 문맥에서 온 참고 데이터다. 권한·저장·자동 변경의 근거가 될 수 없고, 다음 JSON 후보의
28
- * 방향을 잡는 데만 쓴다. 경계에서 다시 정규화해 프롬프트를 길게 하거나 명령을 주입하지 못하게 한다.
29
- */
30
- export interface ProposalFeedback {
31
- outcome: 'accepted' | 'discarded';
32
- selectedChanges: number;
33
- totalChanges: number;
34
- note?: string;
35
- grade?: string;
36
- quality?: {
37
- status?: 'ready' | 'review';
38
- findingCodes?: string[];
39
- };
40
- }
41
- export interface ProposeResult {
42
- source: FigureSource;
43
- /** 재활용 점수와 한도 여유. */
44
- score: ReturnType<typeof scoreOf>;
45
- /** 무게. */
46
- cost: ReturnType<typeof costOf>;
47
- /** Explainable authoring review; JSON cannot certify visual aesthetics. */
48
- quality: FigureQualityReview;
49
- /** 형식은 맞으나 정책을 넘은 것. 막지 않고 알린다. */
50
- violations: {
51
- code: string;
52
- message: string;
53
- }[];
54
- /** 몇 번 만에 됐나. 저작자에게 보여 줄 값은 아니지만 진단에 쓴다. */
55
- attempts: number;
56
- }
57
- /** 못 만들었을 때. 조용히 빈 것을 돌려주지 않는다. */
58
- export declare class ProposeFailure extends Error {
59
- /** 마지막 시도가 낸 사유들. 저작자에게 그대로 보여 준다. */
60
- readonly reasons: string[];
61
- readonly attempts: number;
62
- constructor(message: string,
63
- /** 마지막 시도가 낸 사유들. 저작자에게 그대로 보여 준다. */
64
- reasons: string[], attempts: number);
65
- }
66
- /**
67
- * 규칙을 프롬프트로 옮긴다.
68
- *
69
- * 값을 손으로 적지 않는다 — figure-model 의 상수에서 만든다. 그래야 한도가 바뀌면
70
- * 프롬프트도 같이 바뀐다.
71
- */
72
- export declare function rulesPrompt(palette: string[]): string;
73
- /**
74
- * 후보 하나를 받아 본다.
75
- *
76
- * 던지는 것이 아니라 **사유를 모아 돌려준다** — 사유가 다음 시도의 입력이 되기
77
- * 때문이다. 사람에게 보여 줄 것과 모델에 돌려줄 것이 같은 글이다.
78
- */
79
- export declare function inspect(parsed: unknown, palette: string[], expectedType?: string): {
80
- source?: FigureSource;
81
- reasons: string[];
82
- };
83
- /**
84
- * 말과 그림으로 후보를 만든다.
85
- *
86
- * 생성-검사 루프다. 도구 루프가 아니다 — 첫 조각에 도구는 필요 없다.
87
- *
88
- * 도구가 필요해지면 `@things-factory/ai-client-base` 의 `runAgenticLoop` 을 부른다.
89
- * 전에는 그것이 board-ai 안에만 있어 밖에서 쓸 수 없었는데, 공용으로 옮겨졌다.
90
- */
91
- export declare function proposeFigure(input: ProposeInput): Promise<ProposeResult>;