sonamu 0.7.16 → 0.7.17

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 (64) hide show
  1. package/dist/ai/providers/rtzr/error.d.ts +1 -1
  2. package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
  3. package/dist/api/config.d.ts +1 -0
  4. package/dist/api/config.d.ts.map +1 -1
  5. package/dist/api/config.js +1 -1
  6. package/dist/api/decorators.d.ts +1 -1
  7. package/dist/api/decorators.d.ts.map +1 -1
  8. package/dist/api/decorators.js +1 -1
  9. package/dist/api/sonamu.d.ts +3 -1
  10. package/dist/api/sonamu.d.ts.map +1 -1
  11. package/dist/api/sonamu.js +48 -38
  12. package/dist/syncer/checksum.d.ts +8 -3
  13. package/dist/syncer/checksum.d.ts.map +1 -1
  14. package/dist/syncer/checksum.js +17 -9
  15. package/dist/syncer/code-generator.js +7 -2
  16. package/dist/syncer/syncer.d.ts +6 -6
  17. package/dist/syncer/syncer.d.ts.map +1 -1
  18. package/dist/syncer/syncer.js +27 -13
  19. package/dist/template/implementations/model.template.js +5 -5
  20. package/dist/template/implementations/services.template.d.ts +17 -0
  21. package/dist/template/implementations/services.template.d.ts.map +1 -0
  22. package/dist/template/implementations/services.template.js +159 -0
  23. package/dist/template/implementations/view_form.template.js +2 -2
  24. package/dist/template/implementations/view_id_async_select.template.js +2 -2
  25. package/dist/template/implementations/view_list.template.js +5 -5
  26. package/dist/types/types.d.ts +2 -14
  27. package/dist/types/types.d.ts.map +1 -1
  28. package/dist/types/types.js +3 -15
  29. package/dist/ui/ai-api.d.ts +2 -0
  30. package/dist/ui/ai-api.d.ts.map +1 -1
  31. package/dist/ui/ai-api.js +43 -49
  32. package/dist/ui/ai-client.d.ts +10 -0
  33. package/dist/ui/ai-client.d.ts.map +1 -1
  34. package/dist/ui/ai-client.js +457 -437
  35. package/dist/ui/api.d.ts.map +1 -1
  36. package/dist/ui/api.js +3 -1
  37. package/dist/ui-web/assets/index-DzqUrTB-.js +92 -0
  38. package/dist/ui-web/index.html +1 -1
  39. package/package.json +11 -7
  40. package/src/api/config.ts +3 -0
  41. package/src/api/decorators.ts +6 -1
  42. package/src/api/sonamu.ts +68 -50
  43. package/src/shared/app.shared.ts.txt +1 -1
  44. package/src/shared/web.shared.ts.txt +0 -43
  45. package/src/syncer/checksum.ts +31 -9
  46. package/src/syncer/code-generator.ts +8 -1
  47. package/src/syncer/syncer.ts +38 -26
  48. package/src/template/implementations/model.template.ts +4 -4
  49. package/src/template/implementations/services.template.ts +226 -0
  50. package/src/template/implementations/view_form.template.ts +1 -1
  51. package/src/template/implementations/view_id_async_select.template.ts +1 -1
  52. package/src/template/implementations/view_list.template.ts +4 -4
  53. package/src/types/types.ts +2 -14
  54. package/src/ui/ai-api.ts +61 -60
  55. package/src/ui/ai-client.ts +535 -499
  56. package/src/ui/api.ts +3 -0
  57. package/src/ui/entity.instructions.md +536 -0
  58. package/dist/template/implementations/service.template.d.ts +0 -29
  59. package/dist/template/implementations/service.template.d.ts.map +0 -1
  60. package/dist/template/implementations/service.template.js +0 -202
  61. package/dist/ui-web/assets/index-BcbbB-BB.js +0 -95
  62. package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +0 -1
  63. package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +0 -1
  64. package/src/template/implementations/service.template.ts +0 -328
@@ -1,438 +1,458 @@
1
- // import { anthropic } from "@ai-sdk/anthropic";
2
- // import { type ModelMessage, stepCountIs, streamText, tool } from "ai";
3
- // import assert from "assert";
4
- // import fs from "fs";
5
- // import path from "path";
6
- // import {
7
- // EntityManager,
8
- // type EntityProp,
9
- // type FixtureRecord,
10
- // nonNullable,
11
- // Sonamu,
12
- // TemplateOptions,
13
- // } from "sonamu";
14
- // import { z } from "zod";
15
- // type ValidationError = {
16
- // field: string;
17
- // message: string;
18
- // };
19
- // class AIClient {
20
- // private model = anthropic("claude-sonnet-4-5");
21
- // async init() {
22
- // console.log("AI client initialized with AI SDK");
23
- // }
24
- // handleFixture(messages: ModelMessage[], fixtureRecords: FixtureRecord[]) {
25
- // // 현재 fixtureRecords에서 사용된 엔티티들의 구조 정보 수집
26
- // const usedEntityIds = [...new Set(fixtureRecords.map((r) => r.entityId))];
27
- // const entityStructures = usedEntityIds.map((entityId) => {
28
- // const entity = EntityManager.get(entityId);
29
- // return {
30
- // entityId: entity.id,
31
- // table: entity.table,
32
- // props: entity.props,
33
- // relations: entity.relations,
34
- // enumLabels: entity.enumLabels,
35
- // };
36
- // });
37
- // const systemMessage = `
38
- // 당신은 픽스쳐 레코드를 수정하고 생성할 수 있는 도우미입니다.
39
- // 현재 픽스쳐 레코드:
40
- // ${JSON.stringify(fixtureRecords, null, 2)}
41
- // 엔티티 구조 정보:
42
- // ${JSON.stringify(entityStructures, null, 2)}
43
- // ## 픽스쳐 수정
44
- // 사용자가 픽스쳐 값 수정을 요청하면 updateFixtures 도구를 사용하여 변경사항을 적용하세요.
45
- // - fixtureId: 수정할 픽스쳐 ID (형식: "EntityId#id")
46
- // - updates: 컬럼명을 키로, 새 값을 값으로 하는 객체
47
- // 예시: "User#1" 픽스쳐의 "name" 컬럼을 "홍길동"으로 변경하려면:
48
- // updateFixtures({ updates: [{ fixtureId: "User#1", updates: { name: "홍길동" } }] })
49
- // 변경될 컬럼의 type이 relation인 경우, 관련 엔티티에도 반영되어야 할 컬럼이 있는지 확인하세요.
50
- // ## 픽스쳐 생성
51
- // 사용자가 새로운 픽스쳐 생성을 요청하면 createFixtures 도구를 사용하세요.
52
- // - entityId: 생성할 엔티티 ID
53
- // - id: 새 레코드의 ID (기존 픽스쳐와 중복되지 않는 음수 사용 권장, 예: -1, -2)
54
- // - columns: 컬럼명을 키로, 값을 값으로 하는 객체 (엔티티 구조 참고)
55
- // 예시: 새로운 User 픽스쳐를 생성하려면:
56
- // createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
57
- // `;
58
- // return streamText({
59
- // model: this.model,
60
- // system: systemMessage,
61
- // messages,
62
- // tools: {
63
- // updateFixtures: tool({
64
- // description:
65
- // "픽스쳐 레코드의 값을 수정합니다. 사용자가 특정 컬럼이나 값을 변경해달라고 요청할 때 사용하세요.",
66
- // inputSchema: z.object({
67
- // updates: z.array(
68
- // z.object({
69
- // fixtureId: z.string().describe("수정할 픽스쳐 ID (형식: EntityId#id)"),
70
- // updates: z
71
- // .record(z.string(), z.unknown())
72
- // .describe("컬럼명을 키로, 새 값을 값으로 하는 객체"),
73
- // }),
74
- // ),
75
- // }),
76
- // execute: async ({
77
- // updates,
78
- // }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
79
- // // fixtureRecords를 복사하고 업데이트 적용
80
- // const updatedRecords: FixtureRecord[] = fixtureRecords.map((record) => {
81
- // const update = updates.find((u) => u.fixtureId === record.fixtureId);
82
- // if (update) {
83
- // // columns의 value를 업데이트
84
- // for (const [columnName, newValue] of Object.entries(update.updates)) {
85
- // record.columns[columnName].value =
86
- // newValue as FixtureRecord["columns"][string]["value"];
87
- // }
88
- // return record;
89
- // }
90
- // return record;
91
- // });
92
- // return { success: true, updatedRecords };
93
- // },
94
- // }),
95
- // createFixtures: tool({
96
- // description:
97
- // "새로운 픽스쳐 레코드를 생성합니다. 사용자가 새로운 데이터를 추가해달라고 요청할 때 사용하세요.",
98
- // inputSchema: z.object({
99
- // fixtures: z.array(
100
- // z.object({
101
- // entityId: z.string().describe("생성할 엔티티 ID"),
102
- // id: z.number().describe("새 레코드의 ID (음수 권장, 예: -1, -2)"),
103
- // columns: z
104
- // .record(z.string(), z.unknown())
105
- // .describe("컬럼명을 키로, 값을 값으로 하는 객체"),
106
- // }),
107
- // ),
108
- // }),
109
- // execute: async ({
110
- // fixtures,
111
- // }): Promise<{ success: boolean; updatedRecords: FixtureRecord[] }> => {
112
- // const newRecords: FixtureRecord[] = fixtures.map((fixture) => {
113
- // const entity = EntityManager.get(fixture.entityId);
114
- // // 엔티티 props를 기반으로 columns 구성
115
- // const columns: FixtureRecord["columns"] = {};
116
- // for (const prop of entity.props) {
117
- // if (prop.type === "virtual") continue;
118
- // let value = fixture.columns[prop.name] ?? null;
119
- // if (prop.name === "created_at") {
120
- // // 현재 시간으로 설정
121
- // value = new Date().toISOString();
122
- // } else if (
123
- // prop.type === "relation" &&
124
- // (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")
125
- // ) {
126
- // // 배열로 변환
127
- // value = Array.isArray(value) ? value : [value].filter(nonNullable);
128
- // }
129
- // columns[prop.name] = {
130
- // prop,
131
- // value: value as FixtureRecord["columns"][string]["value"],
132
- // };
133
- // }
134
- // return {
135
- // fixtureId: `${fixture.entityId}#${fixture.id}`,
136
- // entityId: fixture.entityId,
137
- // id: fixture.id,
138
- // columns,
139
- // fetchedRecords: [],
140
- // belongsRecords: [],
141
- // override: false,
142
- // };
143
- // });
144
- // // 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
145
- // for (const newRecord of newRecords) {
146
- // for (const [_colName, col] of Object.entries(newRecord.columns)) {
147
- // if (col.prop.type !== "relation" || col.value === null) continue;
148
- // const relatedEntityId = col.prop.with;
149
- // const relatedIds = Array.isArray(col.value) ? col.value : [col.value];
150
- // for (const relatedId of relatedIds) {
151
- // const relatedFixtureId = `${relatedEntityId}#${relatedId}`;
152
- // const relatedRecord = newRecords.find((r) => r.fixtureId === relatedFixtureId);
153
- // if (relatedRecord) {
154
- // // 역방향 relation 찾기
155
- // const reverseCol = Object.entries(relatedRecord.columns).find(
156
- // ([, c]) => c.prop.type === "relation" && c.prop.with === newRecord.entityId,
157
- // );
158
- // if (reverseCol) {
159
- // const [reverseColName, reverseColValue] = reverseCol;
160
- // const currentValue = reverseColValue.value;
161
- // // 역방향이 배열인 경우 (HasMany, ManyToMany)
162
- // if (
163
- // reverseColValue.prop.type === "relation" &&
164
- // (reverseColValue.prop.relationType === "HasMany" ||
165
- // reverseColValue.prop.relationType === "ManyToMany")
166
- // ) {
167
- // assert(Array.isArray(currentValue), "currentValue must be an array");
168
- // if (!currentValue.includes(newRecord.id)) {
169
- // relatedRecord.columns[reverseColName] = {
170
- // ...reverseColValue,
171
- // value: [...currentValue, newRecord.id],
172
- // };
173
- // }
174
- // } else {
175
- // // 역방향이 단일 값인 경우 (BelongsToOne, OneToOne)
176
- // relatedRecord.columns[reverseColName] = {
177
- // ...reverseColValue,
178
- // value: newRecord.id,
179
- // };
180
- // }
181
- // }
182
- // }
183
- // }
184
- // }
185
- // }
186
- // return { success: true, updatedRecords: newRecords };
187
- // },
188
- // }),
189
- // },
190
- // });
191
- // }
192
- // handleEntity(messages: ModelMessage[]) {
193
- // // entity.instructions.md 파일 읽기
194
- // const instructionsPath = path.join(import.meta.dirname, "..", "entity.instructions.md");
195
- // const instructions = fs.readFileSync(instructionsPath, "utf-8");
196
- // // 현재 등록된 엔티티 정보 수집
197
- // const entityIds = EntityManager.getAllIds();
198
- // const existingEntities = entityIds.map((entityId) => {
199
- // const entity = EntityManager.get(entityId);
200
- // return {
201
- // id: entity.id,
202
- // title: entity.title,
203
- // table: entity.table,
204
- // props: entity.props.map((p) => ({
205
- // name: p.name,
206
- // type: p.type,
207
- // desc: p.desc,
208
- // })),
209
- // };
210
- // });
211
- // const systemMessage = `
212
- // 당신은 Sonamu 프레임워크에서 Entity와 Enum을 생성하는 도우미입니다.
213
- // ${instructions}
214
- // ## 현재 등록된 Entity 목록
215
- // 다른 엔티티와 관계(relation)를 맺거나 subset에서 참조할 때 반드시 아래 정보를 확인하세요.
216
- // ${JSON.stringify(existingEntities, null, 2)}
217
- // ## Tool 사용 가이드
218
- // ### Entity 생성 (createEntity)
219
- // 사용자가 새로운 Entity 생성을 요청하면 createEntity 도구를 사용하세요.
220
- // - entityId: PascalCase로 된 Entity ID (예: "User", "ProductCategory")
221
- // - title: 한글 제목 (예: "사용자", "상품 카테고리")
222
- // - table: snake_case로 된 테이블명 (예: "users", "product_categories")
223
- // - parentId: 부모 Entity ID (선택사항)
224
- // - props: Entity의 프로퍼티 배열 (위 문서의 Property Types 참고)
225
- // - indexes: 인덱스 배열
226
- // - subsets: 서브셋 정의 (기본값: { A: ["id"] })
227
- // - enums: Enum 정의
228
- // ### Entity 수정 (updateEntity)
229
- // 기존 Entity를 수정할 때 updateEntity 도구를 사용하세요. Enum 추가, props 추가/수정, indexes 수정 등 모든 수정 작업에 사용합니다.
230
- // - entityId: 수정할 Entity ID
231
- // - updates: 수정할 필드들 (부분 업데이트)
232
- // - title: 엔티티 한글 제목
233
- // - table: 테이블명
234
- // - props: 추가할 프로퍼티 배열 (기존 props에 추가, 같은 이름이면 교체)
235
- // - indexes: 추가할 인덱스 배열 (기존 indexes에 추가)
236
- // - subsets: 서브셋 정의 (기존 subsets에 병합)
237
- // - enumLabels: Enum 정의 (기존 enumLabels에 병합)
238
- // - mode: "merge"(기본값) 또는 "replace"
239
- // - merge: 기존 값에 병합
240
- // - replace: 해당 필드 전체 교체
241
- // 예시: Employee에 새 Enum 추가
242
- // updateEntity({ entityId: "Employee", updates: { enumLabels: { "EmployeeRole": { "admin": "관리자", "user": "일반" } } } })
243
- // 예시: Project에 새 프로퍼티 추가
244
- // updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type: "integer", desc: "우선순위" }] } })
245
- // ## 필수 사항
246
- // - Entity의 props에는 최소한 id(integer, unsigned), created_at(timestamp)가 포함되어야 합니다.
247
- // - relation 필드는 onUpdate, onDelete가 필수입니다. (예외: OneToOne에서 hasJoinColumn이 false인 경우)
248
- // - Enum ID는 보통 EntityId + 속성명 형태입니다 (예: UserStatus, ProductType)
249
- // - subset에서 다른 엔티티의 프로퍼티를 참조할 때는 반드시 해당 엔티티의 실제 프로퍼티명을 사용하세요.
250
- // ## 검증 오류 처리
251
- // 도구 호출 결과로 검증 오류(validationErrors)가 반환되면:
252
- // 1. 오류 메시지를 분석하여 문제점을 파악하세요.
253
- // 2. 오류를 수정한 데이터로 createEntity를 다시 호출하세요.
254
- // 3. 사용자에게 오류를 그대로 전달하지 말고, 수정 후 재시도하세요.
255
- // ### 일반적인 검증 오류와 수정 방법
256
- // | 오류 메시지 | 수정 방법 |
257
- // |------------|----------|
258
- // | "id 프로퍼티가 필수" | props에 { name: "id", type: "integer", unsigned: true } 추가 |
259
- // | "created_at 프로퍼티가 필수" | props에 { name: "created_at", type: "timestamp", dbDefault: "CURRENT_TIMESTAMP" } 추가 |
260
- // | "XxxOrderBy enum이 필수" | enums에 { "XxxOrderBy": { "id-desc": "ID최신순" } } 추가 |
261
- // | "XxxSearchField enum이 필수" | enums에 { "XxxSearchField": { "id": "ID" } } 추가 |
262
- // | "string 타입은 length가 필수" | 해당 prop에 length 추가 (예: 255) |
263
- // | "text 타입은 textType이 필수" | 해당 prop에 textType 추가 ("text", "mediumtext", "longtext") |
264
- // | "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
265
- // `;
266
- // return streamText({
267
- // model: this.model,
268
- // system: systemMessage,
269
- // messages,
270
- // stopWhen: stepCountIs(2),
271
- // tools: {
272
- // createEntity: tool({
273
- // description:
274
- // "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
275
- // inputSchema: TemplateOptions.shape.entity,
276
- // execute: async (
277
- // entity,
278
- // ): Promise<{
279
- // success: boolean;
280
- // entityId: string;
281
- // error?: string;
282
- // validationErrors?: ValidationError[];
283
- // }> => {
284
- // try {
285
- // // 입력 검증
286
- // const validationErrors = validateEntityJson(entity);
287
- // if (validationErrors.length > 0) {
288
- // return {
289
- // success: false,
290
- // entityId: entity.entityId,
291
- // error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
292
- // validationErrors,
293
- // };
294
- // }
295
- // await Sonamu.syncer.createEntity({
296
- // subsets: { A: ["id"] },
297
- // enums: {},
298
- // ...entity,
299
- // });
300
- // // EntityManager 리로드
301
- // await EntityManager.reload();
302
- // return { success: true, entityId: entity.entityId };
303
- // } catch (e) {
304
- // const error = e instanceof Error ? e.message : "Unknown error";
305
- // return { success: false, entityId: entity.entityId, error };
306
- // }
307
- // },
308
- // }),
309
- // updateEntity: tool({
310
- // description:
311
- // "기존 Entity를 수정합니다. Enum 추가, props 추가/수정, indexes 수정, subsets 수정 등 모든 엔티티 수정 작업에 사용하세요.",
312
- // inputSchema: z.object({
313
- // entityId: z.string().describe("수정할 Entity ID"),
314
- // updates: TemplateOptions.shape.entity.partial().describe("수정할 필드들"),
315
- // mode: z
316
- // .enum(["merge", "replace"])
317
- // .optional()
318
- // .describe("수정 모드: merge(기본값, 기존 값에 병합) 또는 replace(전체 교체)"),
319
- // }),
320
- // execute: async ({
321
- // entityId,
322
- // updates,
323
- // mode = "merge",
324
- // }): Promise<{
325
- // success: boolean;
326
- // entityId: string;
327
- // error?: string;
328
- // validationErrors?: ValidationError[];
329
- // }> => {
330
- // try {
331
- // const entity = EntityManager.get(entityId);
332
- // for (const [key, value] of Object.entries(updates)) {
333
- // if (
334
- // ["entityId", "parentId", "title", "table"].includes(key) &&
335
- // value !== undefined
336
- // ) {
337
- // entity[key] = value;
338
- // }
339
- // }
340
- // // props: merge 시 이름 기준 병합, replace 시 교체
341
- // if (updates.props !== undefined) {
342
- // if (mode === "replace") {
343
- // entity.props = updates.props as EntityProp[];
344
- // } else {
345
- // for (const newProp of updates.props) {
346
- // const existingIndex = entity.props.findIndex((p) => p.name === newProp.name);
347
- // if (existingIndex >= 0) {
348
- // entity.props[existingIndex] = newProp as EntityProp;
349
- // } else {
350
- // entity.props.push(newProp as EntityProp);
351
- // }
352
- // }
353
- // }
354
- // }
355
- // // indexes: merge 시 추가, replace 시 교체
356
- // if (updates.indexes !== undefined) {
357
- // entity.indexes =
358
- // mode === "replace" ? updates.indexes : [...entity.indexes, ...updates.indexes];
359
- // }
360
- // // subsets, enumLabels: assign으로 병합 또는 교체
361
- // if (updates.subsets !== undefined) {
362
- // entity.subsets =
363
- // mode === "replace" ? updates.subsets : { ...entity.subsets, ...updates.subsets };
364
- // }
365
- // if (updates.enums !== undefined) {
366
- // entity.enumLabels =
367
- // mode === "replace" ? updates.enums : { ...entity.enumLabels, ...updates.enums };
368
- // }
369
- // // 저장 전 검증
370
- // const validationErrors = validateEntityJson({
371
- // ...entity,
372
- // entityId: entity.id,
373
- // enums: entity.enumLabels,
374
- // });
375
- // if (validationErrors.length > 0) {
376
- // return {
377
- // success: false,
378
- // entityId,
379
- // error: `검증 오류: ${validationErrors.map((e) => `[${e.field}] ${e.message}`).join(", ")}`,
380
- // validationErrors,
381
- // };
382
- // }
383
- // await entity.save();
384
- // return { success: true, entityId };
385
- // } catch (e) {
386
- // const error = e instanceof Error ? e.message : "Unknown error";
387
- // return { success: false, entityId, error };
388
- // }
389
- // },
390
- // }),
391
- // },
392
- // });
393
- // }
394
- // }
395
- // /**
396
- // * Entity JSON이 entity.instructions.md의 규칙을 따르는지 검증합니다.
397
- // */
398
- // function validateEntityJson(input: TemplateOptions["entity"]): ValidationError[] {
399
- // const errors: ValidationError[] = [];
400
- // const { entityId, props, enums } = input;
401
- // // 1. id, created_at prop 필수
402
- // const hasIdProp = props?.some((p) => p.name === "id");
403
- // if (!hasIdProp) {
404
- // errors.push({ field: "props", message: "id 프로퍼티가 필수입니다." });
405
- // }
406
- // const hasCreatedAtProp = props?.some((p) => p.name === "created_at");
407
- // if (!hasCreatedAtProp) {
408
- // errors.push({ field: "props", message: "created_at 프로퍼티가 필수입니다." });
409
- // }
410
- // // 2. 필수 enum 검증: EntityNameOrderBy, EntityNameSearchField
411
- // const orderByEnumId = `${entityId}OrderBy`;
412
- // const searchFieldEnumId = `${entityId}SearchField`;
413
- // if (!enums?.[orderByEnumId]) {
414
- // errors.push({
415
- // field: "enums",
416
- // message: `${orderByEnumId} enum이 필수입니다. (예: { "id-desc": "ID최신순" })`,
417
- // });
418
- // }
419
- // if (!enums?.[searchFieldEnumId]) {
420
- // errors.push({
421
- // field: "enums",
422
- // message: `${searchFieldEnumId} enum이 필수입니다. (예: { "id": "ID" })`,
423
- // });
424
- // }
425
- // // 3. enum prop의 id가 enums에 정의되어 있는지 확인 (cross-field 검증)
426
- // for (const prop of props ?? []) {
427
- // if (prop.type === "enum" && !enums?.[prop.id]) {
428
- // errors.push({
429
- // field: `props.${prop.name}`,
430
- // message: `enum id "${prop.id}"가 enums에 정의되어 있지 않습니다.`,
431
- // });
432
- // }
433
- // }
434
- // return errors;
435
- // }
436
- // export const aiClient = new AIClient();
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: AI SDK의 타입이 명확하지 않아 any를 허용함 */ import { anthropic } from "@ai-sdk/anthropic";
2
+ import { stepCountIs, streamText, tool } from "ai";
3
+ import assert from "assert";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { z } from "zod";
7
+ import { Sonamu } from "../api/index.js";
8
+ import { EntityManager } from "../entity/entity-manager.js";
9
+ import { isInternalSubsetField, normalizeSubsetField, TemplateOptions } from "../types/types.js";
10
+ import { nonNullable } from "../utils/utils.js";
11
+ class AIClient {
12
+ model = anthropic("claude-sonnet-4-5");
13
+ async init() {
14
+ console.log("AI client initialized with AI SDK");
15
+ }
16
+ handleFixture(messages, fixtureRecords) {
17
+ // 현재 fixtureRecords에서 사용된 엔티티들의 구조 정보 수집
18
+ const usedEntityIds = [
19
+ ...new Set(fixtureRecords.map((r)=>r.entityId))
20
+ ];
21
+ const entityStructures = usedEntityIds.map((entityId)=>{
22
+ const entity = EntityManager.get(entityId);
23
+ return {
24
+ entityId: entity.id,
25
+ table: entity.table,
26
+ props: entity.props,
27
+ relations: entity.relations,
28
+ enumLabels: entity.enumLabels
29
+ };
30
+ });
31
+ const systemMessage = `
32
+ 당신은 픽스쳐 레코드를 수정하고 생성할 수 있는 도우미입니다.
437
33
 
438
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91aS9haS1jbGllbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gaW1wb3J0IHsgYW50aHJvcGljIH0gZnJvbSBcIkBhaS1zZGsvYW50aHJvcGljXCI7XG4vLyBpbXBvcnQgeyB0eXBlIE1vZGVsTWVzc2FnZSwgc3RlcENvdW50SXMsIHN0cmVhbVRleHQsIHRvb2wgfSBmcm9tIFwiYWlcIjtcbi8vIGltcG9ydCBhc3NlcnQgZnJvbSBcImFzc2VydFwiO1xuLy8gaW1wb3J0IGZzIGZyb20gXCJmc1wiO1xuLy8gaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbi8vIGltcG9ydCB7XG4vLyAgIEVudGl0eU1hbmFnZXIsXG4vLyAgIHR5cGUgRW50aXR5UHJvcCxcbi8vICAgdHlwZSBGaXh0dXJlUmVjb3JkLFxuLy8gICBub25OdWxsYWJsZSxcbi8vICAgU29uYW11LFxuLy8gICBUZW1wbGF0ZU9wdGlvbnMsXG4vLyB9IGZyb20gXCJzb25hbXVcIjtcbi8vIGltcG9ydCB7IHogfSBmcm9tIFwiem9kXCI7XG5cbi8vIHR5cGUgVmFsaWRhdGlvbkVycm9yID0ge1xuLy8gICBmaWVsZDogc3RyaW5nO1xuLy8gICBtZXNzYWdlOiBzdHJpbmc7XG4vLyB9O1xuXG4vLyBjbGFzcyBBSUNsaWVudCB7XG4vLyAgIHByaXZhdGUgbW9kZWwgPSBhbnRocm9waWMoXCJjbGF1ZGUtc29ubmV0LTQtNVwiKTtcblxuLy8gICBhc3luYyBpbml0KCkge1xuLy8gICAgIGNvbnNvbGUubG9nKFwiQUkgY2xpZW50IGluaXRpYWxpemVkIHdpdGggQUkgU0RLXCIpO1xuLy8gICB9XG5cbi8vICAgaGFuZGxlRml4dHVyZShtZXNzYWdlczogTW9kZWxNZXNzYWdlW10sIGZpeHR1cmVSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10pIHtcbi8vICAgICAvLyDtmITsnqwgZml4dHVyZVJlY29yZHPsl5DshJwg7IKs7Jqp65CcIOyXlO2LsO2LsOuTpOydmCDqtazsobAg7KCV67O0IOyImOynkVxuLy8gICAgIGNvbnN0IHVzZWRFbnRpdHlJZHMgPSBbLi4ubmV3IFNldChmaXh0dXJlUmVjb3Jkcy5tYXAoKHIpID0+IHIuZW50aXR5SWQpKV07XG4vLyAgICAgY29uc3QgZW50aXR5U3RydWN0dXJlcyA9IHVzZWRFbnRpdHlJZHMubWFwKChlbnRpdHlJZCkgPT4ge1xuLy8gICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZW50aXR5SWQpO1xuXG4vLyAgICAgICByZXR1cm4ge1xuLy8gICAgICAgICBlbnRpdHlJZDogZW50aXR5LmlkLFxuLy8gICAgICAgICB0YWJsZTogZW50aXR5LnRhYmxlLFxuLy8gICAgICAgICBwcm9wczogZW50aXR5LnByb3BzLFxuLy8gICAgICAgICByZWxhdGlvbnM6IGVudGl0eS5yZWxhdGlvbnMsXG4vLyAgICAgICAgIGVudW1MYWJlbHM6IGVudGl0eS5lbnVtTGFiZWxzLFxuLy8gICAgICAgfTtcbi8vICAgICB9KTtcblxuLy8gICAgIGNvbnN0IHN5c3RlbU1lc3NhZ2UgPSBgXG4vLyAgICAgICAgIOuLueyLoOydgCDtlL3siqTss5Ag66CI7L2U65Oc66W8IOyImOygle2VmOqzoCDsg53shLHtlaAg7IiYIOyeiOuKlCDrj4TsmrDrr7jsnoXri4jri6QuXG5cbi8vICAgICAgICAg7ZiE7J6sIO2UveyKpOyzkCDroIjsvZTrk5w6XG4vLyAgICAgICAgICR7SlNPTi5zdHJpbmdpZnkoZml4dHVyZVJlY29yZHMsIG51bGwsIDIpfVxuXG4vLyAgICAgICAgIOyXlO2LsO2LsCDqtazsobAg7KCV67O0OlxuLy8gICAgICAgICAke0pTT04uc3RyaW5naWZ5KGVudGl0eVN0cnVjdHVyZXMsIG51bGwsIDIpfVxuXG4vLyAgICAgICAgICMjIO2UveyKpOyzkCDsiJjsoJVcbi8vICAgICAgICAg7IKs7Jqp7J6Q6rCAIO2UveyKpOyzkCDqsJIg7IiY7KCV7J2EIOyalOyyre2VmOuptCB1cGRhdGVGaXh0dXJlcyDrj4Tqtazrpbwg7IKs7Jqp7ZWY7JesIOuzgOqyveyCrO2VreydhCDsoIHsmqntlZjshLjsmpQuXG4vLyAgICAgICAgIC0gZml4dHVyZUlkOiDsiJjsoJXtlaAg7ZS97Iqk7LOQIElEICjtmJXsi506IFwiRW50aXR5SWQjaWRcIilcbi8vICAgICAgICAgLSB1cGRhdGVzOiDsu6zrn7zrqoXsnYQg7YKk66GcLCDsg4gg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XG5cbi8vICAgICAgICAg7JiI7IucOiBcIlVzZXIjMVwiIO2UveyKpOyzkOydmCBcIm5hbWVcIiDsu6zrn7zsnYQgXCLtmY3quLjrj5lcIuycvOuhnCDrs4Dqsr3tlZjroKTrqbQ6XG4vLyAgICAgICAgIHVwZGF0ZUZpeHR1cmVzKHsgdXBkYXRlczogW3sgZml4dHVyZUlkOiBcIlVzZXIjMVwiLCB1cGRhdGVzOiB7IG5hbWU6IFwi7ZmN6ri464+ZXCIgfSB9XSB9KVxuXG4vLyAgICAgICAgIOuzgOqyveuQoCDsu6zrn7zsnZggdHlwZeydtCByZWxhdGlvbuyduCDqsr3smrAsIOq0gOugqCDsl5Tti7Dti7Dsl5Drj4Qg67CY7JiB65CY7Ja07JW8IO2VoCDsu6zrn7zsnbQg7J6I64qU7KeAIO2ZleyduO2VmOyEuOyalC5cblxuLy8gICAgICAgICAjIyDtlL3siqTss5Ag7IOd7ISxXG4vLyAgICAgICAgIOyCrOyaqeyekOqwgCDsg4jroZzsmrQg7ZS97Iqk7LOQIOyDneyEseydhCDsmpTssq3tlZjrqbQgY3JlYXRlRml4dHVyZXMg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC5cbi8vICAgICAgICAgLSBlbnRpdHlJZDog7IOd7ISx7ZWgIOyXlO2LsO2LsCBJRFxuLy8gICAgICAgICAtIGlkOiDsg4gg66CI7L2U65Oc7J2YIElEICjquLDsobQg7ZS97Iqk7LOQ7JmAIOykkeuzteuQmOyngCDslYrripQg7J2M7IiYIOyCrOyaqSDqtozsnqUsIOyYiDogLTEsIC0yKVxuLy8gICAgICAgICAtIGNvbHVtbnM6IOy7rOufvOuqheydhCDtgqTroZwsIOqwkuydhCDqsJLsnLzroZwg7ZWY64qUIOqwneyytCAo7JeU7Yuw7YuwIOq1rOyhsCDssLjqs6ApXG5cbi8vICAgICAgICAg7JiI7IucOiDsg4jroZzsmrQgVXNlciDtlL3siqTss5Drpbwg7IOd7ISx7ZWY66Ck66m0OlxuLy8gICAgICAgICBjcmVhdGVGaXh0dXJlcyh7IGZpeHR1cmVzOiBbeyBlbnRpdHlJZDogXCJVc2VyXCIsIGlkOiAtMSwgY29sdW1uczogeyBuYW1lOiBcIu2Zjeq4uOuPmVwiLCBlbWFpbDogXCJob25nQGV4YW1wbGUuY29tXCIgfSB9XSB9KVxuLy8gICAgICAgYDtcblxuLy8gICAgIHJldHVybiBzdHJlYW1UZXh0KHtcbi8vICAgICAgIG1vZGVsOiB0aGlzLm1vZGVsLFxuLy8gICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuLy8gICAgICAgbWVzc2FnZXMsXG4vLyAgICAgICB0b29sczoge1xuLy8gICAgICAgICB1cGRhdGVGaXh0dXJlczogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIu2UveyKpOyzkCDroIjsvZTrk5zsnZgg6rCS7J2EIOyImOygle2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIO2KueyglSDsu6zrn7zsnbTrgpgg6rCS7J2EIOuzgOqyve2VtOuLrOudvOqzoCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbi8vICAgICAgICAgICBpbnB1dFNjaGVtYTogei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgdXBkYXRlczogei5hcnJheShcbi8vICAgICAgICAgICAgICAgei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgICAgIGZpeHR1cmVJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCDtlL3siqTss5AgSUQgKO2YleyLnTogRW50aXR5SWQjaWQpXCIpLFxuLy8gICAgICAgICAgICAgICAgIHVwZGF0ZXM6IHpcbi8vICAgICAgICAgICAgICAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgei51bmtub3duKCkpXG4vLyAgICAgICAgICAgICAgICAgICAuZGVzY3JpYmUoXCLsu6zrn7zrqoXsnYQg7YKk66GcLCDsg4gg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XCIpLFxuLy8gICAgICAgICAgICAgICB9KSxcbi8vICAgICAgICAgICAgICksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIHVwZGF0ZXMsXG4vLyAgICAgICAgICAgfSk6IFByb21pc2U8eyBzdWNjZXNzOiBib29sZWFuOyB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdIH0+ID0+IHtcbi8vICAgICAgICAgICAgIC8vIGZpeHR1cmVSZWNvcmRz66W8IOuzteyCrO2VmOqzoCDsl4XrjbDsnbTtirgg7KCB7JqpXG4vLyAgICAgICAgICAgICBjb25zdCB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdID0gZml4dHVyZVJlY29yZHMubWFwKChyZWNvcmQpID0+IHtcbi8vICAgICAgICAgICAgICAgY29uc3QgdXBkYXRlID0gdXBkYXRlcy5maW5kKCh1KSA9PiB1LmZpeHR1cmVJZCA9PT0gcmVjb3JkLmZpeHR1cmVJZCk7XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGUpIHtcbi8vICAgICAgICAgICAgICAgICAvLyBjb2x1bW5z7J2YIHZhbHVl66W8IOyXheuNsOydtO2KuFxuLy8gICAgICAgICAgICAgICAgIGZvciAoY29uc3QgW2NvbHVtbk5hbWUsIG5ld1ZhbHVlXSBvZiBPYmplY3QuZW50cmllcyh1cGRhdGUudXBkYXRlcykpIHtcbi8vICAgICAgICAgICAgICAgICAgIHJlY29yZC5jb2x1bW5zW2NvbHVtbk5hbWVdLnZhbHVlID1cbi8vICAgICAgICAgICAgICAgICAgICAgbmV3VmFsdWUgYXMgRml4dHVyZVJlY29yZFtcImNvbHVtbnNcIl1bc3RyaW5nXVtcInZhbHVlXCJdO1xuLy8gICAgICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICAgICAgICByZXR1cm4gcmVjb3JkO1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgcmV0dXJuIHJlY29yZDtcbi8vICAgICAgICAgICAgIH0pO1xuXG4vLyAgICAgICAgICAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlLCB1cGRhdGVkUmVjb3JkcyB9O1xuLy8gICAgICAgICAgIH0sXG4vLyAgICAgICAgIH0pLFxuLy8gICAgICAgICBjcmVhdGVGaXh0dXJlczogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIuyDiOuhnOyatCDtlL3siqTss5Ag66CI7L2U65Oc66W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDrjbDsnbTthLDrpbwg7LaU6rCA7ZW064us65286rOgIOyalOyyre2VoCDrlYwg7IKs7Jqp7ZWY7IS47JqULlwiLFxuLy8gICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4vLyAgICAgICAgICAgICBmaXh0dXJlczogei5hcnJheShcbi8vICAgICAgICAgICAgICAgei5vYmplY3Qoe1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiB6LnN0cmluZygpLmRlc2NyaWJlKFwi7IOd7ISx7ZWgIOyXlO2LsO2LsCBJRFwiKSxcbi8vICAgICAgICAgICAgICAgICBpZDogei5udW1iZXIoKS5kZXNjcmliZShcIuyDiCDroIjsvZTrk5zsnZggSUQgKOydjOyImCDqtozsnqUsIOyYiDogLTEsIC0yKVwiKSxcbi8vICAgICAgICAgICAgICAgICBjb2x1bW5zOiB6XG4vLyAgICAgICAgICAgICAgICAgICAucmVjb3JkKHouc3RyaW5nKCksIHoudW5rbm93bigpKVxuLy8gICAgICAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7Lus65+866qF7J2EIO2CpOuhnCwg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0XCIpLFxuLy8gICAgICAgICAgICAgICB9KSxcbi8vICAgICAgICAgICAgICksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIGZpeHR1cmVzLFxuLy8gICAgICAgICAgIH0pOiBQcm9taXNlPHsgc3VjY2VzczogYm9vbGVhbjsgdXBkYXRlZFJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSB9PiA9PiB7XG4vLyAgICAgICAgICAgICBjb25zdCBuZXdSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gPSBmaXh0dXJlcy5tYXAoKGZpeHR1cmUpID0+IHtcbi8vICAgICAgICAgICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZml4dHVyZS5lbnRpdHlJZCk7XG5cbi8vICAgICAgICAgICAgICAgLy8g7JeU7Yuw7YuwIHByb3Bz66W8IOq4sOuwmOycvOuhnCBjb2x1bW5zIOq1rOyEsVxuLy8gICAgICAgICAgICAgICBjb25zdCBjb2x1bW5zOiBGaXh0dXJlUmVjb3JkW1wiY29sdW1uc1wiXSA9IHt9O1xuLy8gICAgICAgICAgICAgICBmb3IgKGNvbnN0IHByb3Agb2YgZW50aXR5LnByb3BzKSB7XG4vLyAgICAgICAgICAgICAgICAgaWYgKHByb3AudHlwZSA9PT0gXCJ2aXJ0dWFsXCIpIGNvbnRpbnVlO1xuXG4vLyAgICAgICAgICAgICAgICAgbGV0IHZhbHVlID0gZml4dHVyZS5jb2x1bW5zW3Byb3AubmFtZV0gPz8gbnVsbDtcblxuLy8gICAgICAgICAgICAgICAgIGlmIChwcm9wLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKSB7XG4vLyAgICAgICAgICAgICAgICAgICAvLyDtmITsnqwg7Iuc6rCE7Jy866GcIOyEpOyglVxuLy8gICAgICAgICAgICAgICAgICAgdmFsdWUgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4vLyAgICAgICAgICAgICAgICAgfSBlbHNlIGlmIChcbi8vICAgICAgICAgICAgICAgICAgIHByb3AudHlwZSA9PT0gXCJyZWxhdGlvblwiICYmXG4vLyAgICAgICAgICAgICAgICAgICAocHJvcC5yZWxhdGlvblR5cGUgPT09IFwiSGFzTWFueVwiIHx8IHByb3AucmVsYXRpb25UeXBlID09PSBcIk1hbnlUb01hbnlcIilcbi8vICAgICAgICAgICAgICAgICApIHtcbi8vICAgICAgICAgICAgICAgICAgIC8vIOuwsOyXtOuhnCDrs4DtmZhcbi8vICAgICAgICAgICAgICAgICAgIHZhbHVlID0gQXJyYXkuaXNBcnJheSh2YWx1ZSkgPyB2YWx1ZSA6IFt2YWx1ZV0uZmlsdGVyKG5vbk51bGxhYmxlKTtcbi8vICAgICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgICBjb2x1bW5zW3Byb3AubmFtZV0gPSB7XG4vLyAgICAgICAgICAgICAgICAgICBwcm9wLFxuLy8gICAgICAgICAgICAgICAgICAgdmFsdWU6IHZhbHVlIGFzIEZpeHR1cmVSZWNvcmRbXCJjb2x1bW5zXCJdW3N0cmluZ11bXCJ2YWx1ZVwiXSxcbi8vICAgICAgICAgICAgICAgICB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgcmV0dXJuIHtcbi8vICAgICAgICAgICAgICAgICBmaXh0dXJlSWQ6IGAke2ZpeHR1cmUuZW50aXR5SWR9IyR7Zml4dHVyZS5pZH1gLFxuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiBmaXh0dXJlLmVudGl0eUlkLFxuLy8gICAgICAgICAgICAgICAgIGlkOiBmaXh0dXJlLmlkLFxuLy8gICAgICAgICAgICAgICAgIGNvbHVtbnMsXG4vLyAgICAgICAgICAgICAgICAgZmV0Y2hlZFJlY29yZHM6IFtdLFxuLy8gICAgICAgICAgICAgICAgIGJlbG9uZ3NSZWNvcmRzOiBbXSxcbi8vICAgICAgICAgICAgICAgICBvdmVycmlkZTogZmFsc2UsXG4vLyAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgLy8g7IOIIOugiOy9lOuTnOuTpOydmCByZWxhdGlvbiDsu6zrn7zsnYQg7ZmV7J247ZWY7JesIOq4sOyhtCDroIjsvZTrk5zrk6TsnZgg7Jet67Cp7ZalIHJlbGF0aW9uIOyXheuNsOydtO2KuFxuLy8gICAgICAgICAgICAgZm9yIChjb25zdCBuZXdSZWNvcmQgb2YgbmV3UmVjb3Jkcykge1xuLy8gICAgICAgICAgICAgICBmb3IgKGNvbnN0IFtfY29sTmFtZSwgY29sXSBvZiBPYmplY3QuZW50cmllcyhuZXdSZWNvcmQuY29sdW1ucykpIHtcbi8vICAgICAgICAgICAgICAgICBpZiAoY29sLnByb3AudHlwZSAhPT0gXCJyZWxhdGlvblwiIHx8IGNvbC52YWx1ZSA9PT0gbnVsbCkgY29udGludWU7XG5cbi8vICAgICAgICAgICAgICAgICBjb25zdCByZWxhdGVkRW50aXR5SWQgPSBjb2wucHJvcC53aXRoO1xuLy8gICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRJZHMgPSBBcnJheS5pc0FycmF5KGNvbC52YWx1ZSkgPyBjb2wudmFsdWUgOiBbY29sLnZhbHVlXTtcblxuLy8gICAgICAgICAgICAgICAgIGZvciAoY29uc3QgcmVsYXRlZElkIG9mIHJlbGF0ZWRJZHMpIHtcbi8vICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRGaXh0dXJlSWQgPSBgJHtyZWxhdGVkRW50aXR5SWR9IyR7cmVsYXRlZElkfWA7XG4vLyAgICAgICAgICAgICAgICAgICBjb25zdCByZWxhdGVkUmVjb3JkID0gbmV3UmVjb3Jkcy5maW5kKChyKSA9PiByLmZpeHR1cmVJZCA9PT0gcmVsYXRlZEZpeHR1cmVJZCk7XG5cbi8vICAgICAgICAgICAgICAgICAgIGlmIChyZWxhdGVkUmVjb3JkKSB7XG4vLyAgICAgICAgICAgICAgICAgICAgIC8vIOyXreuwqe2WpSByZWxhdGlvbiDssL7quLBcbi8vICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmV2ZXJzZUNvbCA9IE9iamVjdC5lbnRyaWVzKHJlbGF0ZWRSZWNvcmQuY29sdW1ucykuZmluZChcbi8vICAgICAgICAgICAgICAgICAgICAgICAoWywgY10pID0+IGMucHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiYgYy5wcm9wLndpdGggPT09IG5ld1JlY29yZC5lbnRpdHlJZCxcbi8vICAgICAgICAgICAgICAgICAgICAgKTtcblxuLy8gICAgICAgICAgICAgICAgICAgICBpZiAocmV2ZXJzZUNvbCkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IFtyZXZlcnNlQ29sTmFtZSwgcmV2ZXJzZUNvbFZhbHVlXSA9IHJldmVyc2VDb2w7XG4vLyAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgY3VycmVudFZhbHVlID0gcmV2ZXJzZUNvbFZhbHVlLnZhbHVlO1xuXG4vLyAgICAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7Zal7J20IOuwsOyXtOyduCDqsr3smrAgKEhhc01hbnksIE1hbnlUb01hbnkpXG4vLyAgICAgICAgICAgICAgICAgICAgICAgaWYgKFxuLy8gICAgICAgICAgICAgICAgICAgICAgICAgcmV2ZXJzZUNvbFZhbHVlLnByb3AudHlwZSA9PT0gXCJyZWxhdGlvblwiICYmXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAocmV2ZXJzZUNvbFZhbHVlLnByb3AucmVsYXRpb25UeXBlID09PSBcIkhhc01hbnlcIiB8fFxuLy8gICAgICAgICAgICAgICAgICAgICAgICAgICByZXZlcnNlQ29sVmFsdWUucHJvcC5yZWxhdGlvblR5cGUgPT09IFwiTWFueVRvTWFueVwiKVxuLy8gICAgICAgICAgICAgICAgICAgICAgICkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgICAgYXNzZXJ0KEFycmF5LmlzQXJyYXkoY3VycmVudFZhbHVlKSwgXCJjdXJyZW50VmFsdWUgbXVzdCBiZSBhbiBhcnJheVwiKTtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghY3VycmVudFZhbHVlLmluY2x1ZGVzKG5ld1JlY29yZC5pZCkpIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFJlY29yZC5jb2x1bW5zW3JldmVyc2VDb2xOYW1lXSA9IHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi5yZXZlcnNlQ29sVmFsdWUsXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IFsuLi5jdXJyZW50VmFsdWUsIG5ld1JlY29yZC5pZF0sXG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIC8vIOyXreuwqe2WpeydtCDri6jsnbwg6rCS7J24IOqyveyasCAoQmVsb25nc1RvT25lLCBPbmVUb09uZSlcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0ZWRSZWNvcmQuY29sdW1uc1tyZXZlcnNlQ29sTmFtZV0gPSB7XG4vLyAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLnJldmVyc2VDb2xWYWx1ZSxcbi8vICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IG5ld1JlY29yZC5pZCxcbi8vICAgICAgICAgICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHVwZGF0ZWRSZWNvcmRzOiBuZXdSZWNvcmRzIH07XG4vLyAgICAgICAgICAgfSxcbi8vICAgICAgICAgfSksXG4vLyAgICAgICB9LFxuLy8gICAgIH0pO1xuLy8gICB9XG5cbi8vICAgaGFuZGxlRW50aXR5KG1lc3NhZ2VzOiBNb2RlbE1lc3NhZ2VbXSkge1xuLy8gICAgIC8vIGVudGl0eS5pbnN0cnVjdGlvbnMubWQg7YyM7J28IOydveq4sFxuLy8gICAgIGNvbnN0IGluc3RydWN0aW9uc1BhdGggPSBwYXRoLmpvaW4oaW1wb3J0Lm1ldGEuZGlybmFtZSwgXCIuLlwiLCBcImVudGl0eS5pbnN0cnVjdGlvbnMubWRcIik7XG4vLyAgICAgY29uc3QgaW5zdHJ1Y3Rpb25zID0gZnMucmVhZEZpbGVTeW5jKGluc3RydWN0aW9uc1BhdGgsIFwidXRmLThcIik7XG5cbi8vICAgICAvLyDtmITsnqwg65Ox66Gd65CcIOyXlO2LsO2LsCDsoJXrs7Qg7IiY7KeRXG4vLyAgICAgY29uc3QgZW50aXR5SWRzID0gRW50aXR5TWFuYWdlci5nZXRBbGxJZHMoKTtcbi8vICAgICBjb25zdCBleGlzdGluZ0VudGl0aWVzID0gZW50aXR5SWRzLm1hcCgoZW50aXR5SWQpID0+IHtcbi8vICAgICAgIGNvbnN0IGVudGl0eSA9IEVudGl0eU1hbmFnZXIuZ2V0KGVudGl0eUlkKTtcbi8vICAgICAgIHJldHVybiB7XG4vLyAgICAgICAgIGlkOiBlbnRpdHkuaWQsXG4vLyAgICAgICAgIHRpdGxlOiBlbnRpdHkudGl0bGUsXG4vLyAgICAgICAgIHRhYmxlOiBlbnRpdHkudGFibGUsXG4vLyAgICAgICAgIHByb3BzOiBlbnRpdHkucHJvcHMubWFwKChwKSA9PiAoe1xuLy8gICAgICAgICAgIG5hbWU6IHAubmFtZSxcbi8vICAgICAgICAgICB0eXBlOiBwLnR5cGUsXG4vLyAgICAgICAgICAgZGVzYzogcC5kZXNjLFxuLy8gICAgICAgICB9KSksXG4vLyAgICAgICB9O1xuLy8gICAgIH0pO1xuXG4vLyAgICAgY29uc3Qgc3lzdGVtTWVzc2FnZSA9IGBcbi8vIOuLueyLoOydgCBTb25hbXUg7ZSE66CI7J6E7JuM7YGs7JeQ7IScIEVudGl0eeyZgCBFbnVt7J2EIOyDneyEse2VmOuKlCDrj4TsmrDrr7jsnoXri4jri6QuXG5cbi8vICR7aW5zdHJ1Y3Rpb25zfVxuXG4vLyAjIyDtmITsnqwg65Ox66Gd65CcIEVudGl0eSDrqqnroZ1cbi8vIOuLpOuluCDsl5Tti7Dti7DsmYAg6rSA6rOEKHJlbGF0aW9uKeulvCDrp7rqsbDrgpggc3Vic2V07JeQ7IScIOywuOyhsO2VoCDrlYwg67CY65Oc7IucIOyVhOuemCDsoJXrs7Trpbwg7ZmV7J247ZWY7IS47JqULlxuXG4vLyAke0pTT04uc3RyaW5naWZ5KGV4aXN0aW5nRW50aXRpZXMsIG51bGwsIDIpfVxuXG4vLyAjIyBUb29sIOyCrOyaqSDqsIDsnbTrk5xcblxuLy8gIyMjIEVudGl0eSDsg53shLEgKGNyZWF0ZUVudGl0eSlcbi8vIOyCrOyaqeyekOqwgCDsg4jroZzsmrQgRW50aXR5IOyDneyEseydhCDsmpTssq3tlZjrqbQgY3JlYXRlRW50aXR5IOuPhOq1rOulvCDsgqzsmqntlZjshLjsmpQuXG4vLyAtIGVudGl0eUlkOiBQYXNjYWxDYXNl66GcIOuQnCBFbnRpdHkgSUQgKOyYiDogXCJVc2VyXCIsIFwiUHJvZHVjdENhdGVnb3J5XCIpXG4vLyAtIHRpdGxlOiDtlZzquIAg7KCc66qpICjsmIg6IFwi7IKs7Jqp7J6QXCIsIFwi7IOB7ZKIIOy5tO2FjOqzoOumrFwiKVxuLy8gLSB0YWJsZTogc25ha2VfY2FzZeuhnCDrkJwg7YWM7J2067iU66qFICjsmIg6IFwidXNlcnNcIiwgXCJwcm9kdWN0X2NhdGVnb3JpZXNcIilcbi8vIC0gcGFyZW50SWQ6IOu2gOuqqCBFbnRpdHkgSUQgKOyEoO2DneyCrO2VrSlcbi8vIC0gcHJvcHM6IEVudGl0eeydmCDtlITroZztjbzti7Ag67Cw7Je0ICjsnIQg66y47ISc7J2YIFByb3BlcnR5IFR5cGVzIOywuOqzoClcbi8vIC0gaW5kZXhlczog7J24642x7IqkIOuwsOyXtFxuLy8gLSBzdWJzZXRzOiDshJzruIzshYsg7KCV7J2YICjquLDrs7jqsJI6IHsgQTogW1wiaWRcIl0gfSlcbi8vIC0gZW51bXM6IEVudW0g7KCV7J2YXG5cbi8vICMjIyBFbnRpdHkg7IiY7KCVICh1cGRhdGVFbnRpdHkpXG4vLyDquLDsobQgRW50aXR566W8IOyImOygle2VoCDrlYwgdXBkYXRlRW50aXR5IOuPhOq1rOulvCDsgqzsmqntlZjshLjsmpQuIEVudW0g7LaU6rCALCBwcm9wcyDstpTqsIAv7IiY7KCVLCBpbmRleGVzIOyImOyglSDrk7Eg66qo65OgIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWp64uI64ukLlxuLy8gLSBlbnRpdHlJZDog7IiY7KCV7ZWgIEVudGl0eSBJRFxuLy8gLSB1cGRhdGVzOiDsiJjsoJXtlaAg7ZWE65Oc65OkICjrtoDrtoQg7JeF642w7J207Yq4KVxuLy8gICAtIHRpdGxlOiDsl5Tti7Dti7Ag7ZWc6riAIOygnOuqqVxuLy8gICAtIHRhYmxlOiDthYzsnbTruJTrqoVcbi8vICAgLSBwcm9wczog7LaU6rCA7ZWgIO2UhOuhnO2NvO2LsCDrsLDsl7QgKOq4sOyhtCBwcm9wc+yXkCDstpTqsIAsIOqwmeydgCDsnbTrpoTsnbTrqbQg6rWQ7LK0KVxuLy8gICAtIGluZGV4ZXM6IOy2lOqwgO2VoCDsnbjrjbHsiqQg67Cw7Je0ICjquLDsobQgaW5kZXhlc+yXkCDstpTqsIApXG4vLyAgIC0gc3Vic2V0czog7ISc67iM7IWLIOygleydmCAo6riw7KG0IHN1YnNldHPsl5Ag67OR7ZWpKVxuLy8gICAtIGVudW1MYWJlbHM6IEVudW0g7KCV7J2YICjquLDsobQgZW51bUxhYmVsc+yXkCDrs5HtlakpXG4vLyAtIG1vZGU6IFwibWVyZ2VcIijquLDrs7jqsJIpIOuYkOuKlCBcInJlcGxhY2VcIlxuLy8gICAtIG1lcmdlOiDquLDsobQg6rCS7JeQIOuzke2VqVxuLy8gICAtIHJlcGxhY2U6IO2VtOuLuSDtlYTrk5wg7KCE7LK0IOq1kOyytFxuXG4vLyDsmIjsi5w6IEVtcGxveWVl7JeQIOyDiCBFbnVtIOy2lOqwgFxuLy8gdXBkYXRlRW50aXR5KHsgZW50aXR5SWQ6IFwiRW1wbG95ZWVcIiwgdXBkYXRlczogeyBlbnVtTGFiZWxzOiB7IFwiRW1wbG95ZWVSb2xlXCI6IHsgXCJhZG1pblwiOiBcIuq0gOumrOyekFwiLCBcInVzZXJcIjogXCLsnbzrsJhcIiB9IH0gfSB9KVxuXG4vLyDsmIjsi5w6IFByb2plY3Tsl5Ag7IOIIO2UhOuhnO2NvO2LsCDstpTqsIBcbi8vIHVwZGF0ZUVudGl0eSh7IGVudGl0eUlkOiBcIlByb2plY3RcIiwgdXBkYXRlczogeyBwcm9wczogW3sgbmFtZTogXCJwcmlvcml0eVwiLCB0eXBlOiBcImludGVnZXJcIiwgZGVzYzogXCLsmrDshKDsiJzsnIRcIiB9XSB9IH0pXG5cbi8vICMjIO2VhOyImCDsgqztla1cbi8vIC0gRW50aXR57J2YIHByb3Bz7JeQ64qUIOy1nOyGjO2VnCBpZChpbnRlZ2VyLCB1bnNpZ25lZCksIGNyZWF0ZWRfYXQodGltZXN0YW1wKeqwgCDtj6ztlajrkJjslrTslbwg7ZWp64uI64ukLlxuLy8gLSByZWxhdGlvbiDtlYTrk5zripQgb25VcGRhdGUsIG9uRGVsZXRl6rCAIO2VhOyImOyeheuLiOuLpC4gKOyYiOyZuDogT25lVG9PbmXsl5DshJwgaGFzSm9pbkNvbHVtbuydtCBmYWxzZeyduCDqsr3smrApXG4vLyAtIEVudW0gSUTripQg67O07Ya1IEVudGl0eUlkICsg7IaN7ISx66qFIO2Yle2DnOyeheuLiOuLpCAo7JiIOiBVc2VyU3RhdHVzLCBQcm9kdWN0VHlwZSlcbi8vIC0gc3Vic2V07JeQ7IScIOuLpOuluCDsl5Tti7Dti7DsnZgg7ZSE66Gc7Y287Yuw66W8IOywuOyhsO2VoCDrlYzripQg67CY65Oc7IucIO2VtOuLuSDsl5Tti7Dti7DsnZgg7Iuk7KCcIO2UhOuhnO2NvO2LsOuqheydhCDsgqzsmqntlZjshLjsmpQuXG5cbi8vICMjIOqygOymnSDsmKTrpZgg7LKY66asXG4vLyDrj4Tqtawg7Zi47LacIOqysOqzvOuhnCDqsoDspp0g7Jik66WYKHZhbGlkYXRpb25FcnJvcnMp6rCAIOuwmO2ZmOuQmOuptDpcbi8vIDEuIOyYpOulmCDrqZTsi5zsp4Drpbwg67aE7ISd7ZWY7JesIOusuOygnOygkOydhCDtjIzslYXtlZjshLjsmpQuXG4vLyAyLiDsmKTrpZjrpbwg7IiY7KCV7ZWcIOuNsOydtO2EsOuhnCBjcmVhdGVFbnRpdHnrpbwg64uk7IucIO2YuOy2nO2VmOyEuOyalC5cbi8vIDMuIOyCrOyaqeyekOyXkOqyjCDsmKTrpZjrpbwg6re464yA66GcIOyghOuLrO2VmOyngCDrp5Dqs6AsIOyImOyglSDtm4Qg7J6s7Iuc64+E7ZWY7IS47JqULlxuXG4vLyAjIyMg7J2867CY7KCB7J24IOqygOymnSDsmKTrpZjsmYAg7IiY7KCVIOuwqeuylVxuLy8gfCDsmKTrpZgg66mU7Iuc7KeAIHwg7IiY7KCVIOuwqeuylSB8XG4vLyB8LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18XG4vLyB8IFwiaWQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImFwiIHwgcHJvcHPsl5AgeyBuYW1lOiBcImlkXCIsIHR5cGU6IFwiaW50ZWdlclwiLCB1bnNpZ25lZDogdHJ1ZSB9IOy2lOqwgCB8XG4vLyB8IFwiY3JlYXRlZF9hdCDtlITroZztjbzti7DqsIAg7ZWE7IiYXCIgfCBwcm9wc+yXkCB7IG5hbWU6IFwiY3JlYXRlZF9hdFwiLCB0eXBlOiBcInRpbWVzdGFtcFwiLCBkYkRlZmF1bHQ6IFwiQ1VSUkVOVF9USU1FU1RBTVBcIiB9IOy2lOqwgCB8XG4vLyB8IFwiWHh4T3JkZXJCeSBlbnVt7J20IO2VhOyImFwiIHwgZW51bXPsl5AgeyBcIlh4eE9yZGVyQnlcIjogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0gfSDstpTqsIAgfFxuLy8gfCBcIlh4eFNlYXJjaEZpZWxkIGVudW3snbQg7ZWE7IiYXCIgfCBlbnVtc+yXkCB7IFwiWHh4U2VhcmNoRmllbGRcIjogeyBcImlkXCI6IFwiSURcIiB9IH0g7LaU6rCAIHxcbi8vIHwgXCJzdHJpbmcg7YOA7J6F7J2AIGxlbmd0aOqwgCDtlYTsiJhcIiB8IO2VtOuLuSBwcm9w7JeQIGxlbmd0aCDstpTqsIAgKOyYiDogMjU1KSB8XG4vLyB8IFwidGV4dCDtg4DsnoXsnYAgdGV4dFR5cGXsnbQg7ZWE7IiYXCIgfCDtlbTri7kgcHJvcOyXkCB0ZXh0VHlwZSDstpTqsIAgKFwidGV4dFwiLCBcIm1lZGl1bXRleHRcIiwgXCJsb25ndGV4dFwiKSB8XG4vLyB8IFwib25VcGRhdGXqsIAg7ZWE7IiYXCIgfCDtlbTri7kgcmVsYXRpb24gcHJvcOyXkCBvblVwZGF0ZSwgb25EZWxldGUg7LaU6rCAIChcIkNBU0NBREVcIikgfFxuLy8gICAgICAgYDtcblxuLy8gICAgIHJldHVybiBzdHJlYW1UZXh0KHtcbi8vICAgICAgIG1vZGVsOiB0aGlzLm1vZGVsLFxuLy8gICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuLy8gICAgICAgbWVzc2FnZXMsXG4vLyAgICAgICBzdG9wV2hlbjogc3RlcENvdW50SXMoMiksXG4vLyAgICAgICB0b29sczoge1xuLy8gICAgICAgICBjcmVhdGVFbnRpdHk6IHRvb2woe1xuLy8gICAgICAgICAgIGRlc2NyaXB0aW9uOlxuLy8gICAgICAgICAgICAgXCLsg4jroZzsmrQgRW50aXR566W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDsl5Tti7Dti7Drgpgg7YWM7J2067iUIOyDneyEseydhCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbi8vICAgICAgICAgICBpbnB1dFNjaGVtYTogVGVtcGxhdGVPcHRpb25zLnNoYXBlLmVudGl0eSxcbi8vICAgICAgICAgICBleGVjdXRlOiBhc3luYyAoXG4vLyAgICAgICAgICAgICBlbnRpdHksXG4vLyAgICAgICAgICAgKTogUHJvbWlzZTx7XG4vLyAgICAgICAgICAgICBzdWNjZXNzOiBib29sZWFuO1xuLy8gICAgICAgICAgICAgZW50aXR5SWQ6IHN0cmluZztcbi8vICAgICAgICAgICAgIGVycm9yPzogc3RyaW5nO1xuLy8gICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycz86IFZhbGlkYXRpb25FcnJvcltdO1xuLy8gICAgICAgICAgIH0+ID0+IHtcbi8vICAgICAgICAgICAgIHRyeSB7XG4vLyAgICAgICAgICAgICAgIC8vIOyeheugpSDqsoDspp1cbi8vICAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbihlbnRpdHkpO1xuXG4vLyAgICAgICAgICAgICAgIGlmICh2YWxpZGF0aW9uRXJyb3JzLmxlbmd0aCA+IDApIHtcbi8vICAgICAgICAgICAgICAgICByZXR1cm4ge1xuLy8gICAgICAgICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuLy8gICAgICAgICAgICAgICAgICAgZXJyb3I6IGDqsoDspp0g7Jik66WYOiAke3ZhbGlkYXRpb25FcnJvcnMubWFwKChlKSA9PiBgWyR7ZS5maWVsZH1dICR7ZS5tZXNzYWdlfWApLmpvaW4oXCIsIFwiKX1gLFxuLy8gICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycyxcbi8vICAgICAgICAgICAgICAgICB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgYXdhaXQgU29uYW11LnN5bmNlci5jcmVhdGVFbnRpdHkoe1xuLy8gICAgICAgICAgICAgICAgIHN1YnNldHM6IHsgQTogW1wiaWRcIl0gfSxcbi8vICAgICAgICAgICAgICAgICBlbnVtczoge30sXG4vLyAgICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuLy8gICAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgICAvLyBFbnRpdHlNYW5hZ2VyIOumrOuhnOuTnFxuLy8gICAgICAgICAgICAgICBhd2FpdCBFbnRpdHlNYW5hZ2VyLnJlbG9hZCgpO1xuXG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQgfTtcbi8vICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbi8vICAgICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBlIGluc3RhbmNlb2YgRXJyb3IgPyBlLm1lc3NhZ2UgOiBcIlVua25vd24gZXJyb3JcIjtcbi8vICAgICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQsIGVycm9yIH07XG4vLyAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgfSxcbi8vICAgICAgICAgfSksXG4vLyAgICAgICAgIHVwZGF0ZUVudGl0eTogdG9vbCh7XG4vLyAgICAgICAgICAgZGVzY3JpcHRpb246XG4vLyAgICAgICAgICAgICBcIuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWp64uI64ukLiBFbnVtIOy2lOqwgCwgcHJvcHMg7LaU6rCAL+yImOyglSwgaW5kZXhlcyDsiJjsoJUsIHN1YnNldHMg7IiY7KCVIOuTsSDrqqjrk6Ag7JeU7Yuw7YuwIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWY7IS47JqULlwiLFxuLy8gICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4vLyAgICAgICAgICAgICBlbnRpdHlJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCBFbnRpdHkgSURcIiksXG4vLyAgICAgICAgICAgICB1cGRhdGVzOiBUZW1wbGF0ZU9wdGlvbnMuc2hhcGUuZW50aXR5LnBhcnRpYWwoKS5kZXNjcmliZShcIuyImOygle2VoCDtlYTrk5zrk6RcIiksXG4vLyAgICAgICAgICAgICBtb2RlOiB6XG4vLyAgICAgICAgICAgICAgIC5lbnVtKFtcIm1lcmdlXCIsIFwicmVwbGFjZVwiXSlcbi8vICAgICAgICAgICAgICAgLm9wdGlvbmFsKClcbi8vICAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7IiY7KCVIOuqqOuTnDogbWVyZ2Uo6riw67O46rCSLCDquLDsobQg6rCS7JeQIOuzke2VqSkg65iQ64qUIHJlcGxhY2Uo7KCE7LK0IOq1kOyytClcIiksXG4vLyAgICAgICAgICAgfSksXG4vLyAgICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbi8vICAgICAgICAgICAgIGVudGl0eUlkLFxuLy8gICAgICAgICAgICAgdXBkYXRlcyxcbi8vICAgICAgICAgICAgIG1vZGUgPSBcIm1lcmdlXCIsXG4vLyAgICAgICAgICAgfSk6IFByb21pc2U8e1xuLy8gICAgICAgICAgICAgc3VjY2VzczogYm9vbGVhbjtcbi8vICAgICAgICAgICAgIGVudGl0eUlkOiBzdHJpbmc7XG4vLyAgICAgICAgICAgICBlcnJvcj86IHN0cmluZztcbi8vICAgICAgICAgICAgIHZhbGlkYXRpb25FcnJvcnM/OiBWYWxpZGF0aW9uRXJyb3JbXTtcbi8vICAgICAgICAgICB9PiA9PiB7XG4vLyAgICAgICAgICAgICB0cnkge1xuLy8gICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbi8vICAgICAgICAgICAgICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXModXBkYXRlcykpIHtcbi8vICAgICAgICAgICAgICAgICBpZiAoXG4vLyAgICAgICAgICAgICAgICAgICBbXCJlbnRpdHlJZFwiLCBcInBhcmVudElkXCIsIFwidGl0bGVcIiwgXCJ0YWJsZVwiXS5pbmNsdWRlcyhrZXkpICYmXG4vLyAgICAgICAgICAgICAgICAgICB2YWx1ZSAhPT0gdW5kZWZpbmVkXG4vLyAgICAgICAgICAgICAgICAgKSB7XG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHlba2V5XSA9IHZhbHVlO1xuLy8gICAgICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICAgICAgfVxuXG4vLyAgICAgICAgICAgICAgIC8vIHByb3BzOiBtZXJnZSDsi5wg7J2066aEIOq4sOykgCDrs5HtlaksIHJlcGxhY2Ug7IucIOq1kOyytFxuLy8gICAgICAgICAgICAgICBpZiAodXBkYXRlcy5wcm9wcyAhPT0gdW5kZWZpbmVkKSB7XG4vLyAgICAgICAgICAgICAgICAgaWYgKG1vZGUgPT09IFwicmVwbGFjZVwiKSB7XG4vLyAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHMgPSB1cGRhdGVzLnByb3BzIGFzIEVudGl0eVByb3BbXTtcbi8vICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuLy8gICAgICAgICAgICAgICAgICAgZm9yIChjb25zdCBuZXdQcm9wIG9mIHVwZGF0ZXMucHJvcHMpIHtcbi8vICAgICAgICAgICAgICAgICAgICAgY29uc3QgZXhpc3RpbmdJbmRleCA9IGVudGl0eS5wcm9wcy5maW5kSW5kZXgoKHApID0+IHAubmFtZSA9PT0gbmV3UHJvcC5uYW1lKTtcbi8vICAgICAgICAgICAgICAgICAgICAgaWYgKGV4aXN0aW5nSW5kZXggPj0gMCkge1xuLy8gICAgICAgICAgICAgICAgICAgICAgIGVudGl0eS5wcm9wc1tleGlzdGluZ0luZGV4XSA9IG5ld1Byb3AgYXMgRW50aXR5UHJvcDtcbi8vICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbi8vICAgICAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHMucHVzaChuZXdQcm9wIGFzIEVudGl0eVByb3ApO1xuLy8gICAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgICB9XG4vLyAgICAgICAgICAgICAgICAgfVxuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8gaW5kZXhlczogbWVyZ2Ug7IucIOy2lOqwgCwgcmVwbGFjZSDsi5wg6rWQ7LK0XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGVzLmluZGV4ZXMgIT09IHVuZGVmaW5lZCkge1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eS5pbmRleGVzID1cbi8vICAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiID8gdXBkYXRlcy5pbmRleGVzIDogWy4uLmVudGl0eS5pbmRleGVzLCAuLi51cGRhdGVzLmluZGV4ZXNdO1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8gc3Vic2V0cywgZW51bUxhYmVsczogYXNzaWdu7Jy866GcIOuzke2VqSDrmJDripQg6rWQ7LK0XG4vLyAgICAgICAgICAgICAgIGlmICh1cGRhdGVzLnN1YnNldHMgIT09IHVuZGVmaW5lZCkge1xuLy8gICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzID1cbi8vICAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiID8gdXBkYXRlcy5zdWJzZXRzIDogeyAuLi5lbnRpdHkuc3Vic2V0cywgLi4udXBkYXRlcy5zdWJzZXRzIH07XG4vLyAgICAgICAgICAgICAgIH1cblxuLy8gICAgICAgICAgICAgICBpZiAodXBkYXRlcy5lbnVtcyAhPT0gdW5kZWZpbmVkKSB7XG4vLyAgICAgICAgICAgICAgICAgZW50aXR5LmVudW1MYWJlbHMgPVxuLy8gICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCIgPyB1cGRhdGVzLmVudW1zIDogeyAuLi5lbnRpdHkuZW51bUxhYmVscywgLi4udXBkYXRlcy5lbnVtcyB9O1xuLy8gICAgICAgICAgICAgICB9XG5cbi8vICAgICAgICAgICAgICAgLy8g7KCA7J6lIOyghCDqsoDspp1cbi8vICAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbih7XG4vLyAgICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuLy8gICAgICAgICAgICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4vLyAgICAgICAgICAgICAgICAgZW51bXM6IGVudGl0eS5lbnVtTGFiZWxzLFxuLy8gICAgICAgICAgICAgICB9KTtcblxuLy8gICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvbkVycm9ycy5sZW5ndGggPiAwKSB7XG4vLyAgICAgICAgICAgICAgICAgcmV0dXJuIHtcbi8vICAgICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuLy8gICAgICAgICAgICAgICAgICAgZW50aXR5SWQsXG4vLyAgICAgICAgICAgICAgICAgICBlcnJvcjogYOqygOymnSDsmKTrpZg6ICR7dmFsaWRhdGlvbkVycm9ycy5tYXAoKGUpID0+IGBbJHtlLmZpZWxkfV0gJHtlLm1lc3NhZ2V9YCkuam9pbihcIiwgXCIpfWAsXG4vLyAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uRXJyb3JzLFxuLy8gICAgICAgICAgICAgICAgIH07XG4vLyAgICAgICAgICAgICAgIH1cblxuLy8gICAgICAgICAgICAgICBhd2FpdCBlbnRpdHkuc2F2ZSgpO1xuXG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkIH07XG4vLyAgICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4vLyAgICAgICAgICAgICAgIGNvbnN0IGVycm9yID0gZSBpbnN0YW5jZW9mIEVycm9yID8gZS5tZXNzYWdlIDogXCJVbmtub3duIGVycm9yXCI7XG4vLyAgICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlbnRpdHlJZCwgZXJyb3IgfTtcbi8vICAgICAgICAgICAgIH1cbi8vICAgICAgICAgICB9LFxuLy8gICAgICAgICB9KSxcbi8vICAgICAgIH0sXG4vLyAgICAgfSk7XG4vLyAgIH1cbi8vIH1cblxuLy8gLyoqXG4vLyAgKiBFbnRpdHkgSlNPTuydtCBlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1k7J2YIOq3nOy5meydhCDrlLDrpbTripTsp4Ag6rKA7Kad7ZWp64uI64ukLlxuLy8gICovXG4vLyBmdW5jdGlvbiB2YWxpZGF0ZUVudGl0eUpzb24oaW5wdXQ6IFRlbXBsYXRlT3B0aW9uc1tcImVudGl0eVwiXSk6IFZhbGlkYXRpb25FcnJvcltdIHtcbi8vICAgY29uc3QgZXJyb3JzOiBWYWxpZGF0aW9uRXJyb3JbXSA9IFtdO1xuLy8gICBjb25zdCB7IGVudGl0eUlkLCBwcm9wcywgZW51bXMgfSA9IGlucHV0O1xuXG4vLyAgIC8vIDEuIGlkLCBjcmVhdGVkX2F0IHByb3Ag7ZWE7IiYXG4vLyAgIGNvbnN0IGhhc0lkUHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiaWRcIik7XG4vLyAgIGlmICghaGFzSWRQcm9wKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImlkIO2UhOuhnO2NvO2LsOqwgCDtlYTsiJjsnoXri4jri6QuXCIgfSk7XG4vLyAgIH1cbi8vICAgY29uc3QgaGFzQ3JlYXRlZEF0UHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKTtcbi8vICAgaWYgKCFoYXNDcmVhdGVkQXRQcm9wKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImNyZWF0ZWRfYXQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImOyeheuLiOuLpC5cIiB9KTtcbi8vICAgfVxuXG4vLyAgIC8vIDIuIO2VhOyImCBlbnVtIOqygOymnTogRW50aXR5TmFtZU9yZGVyQnksIEVudGl0eU5hbWVTZWFyY2hGaWVsZFxuLy8gICBjb25zdCBvcmRlckJ5RW51bUlkID0gYCR7ZW50aXR5SWR9T3JkZXJCeWA7XG4vLyAgIGNvbnN0IHNlYXJjaEZpZWxkRW51bUlkID0gYCR7ZW50aXR5SWR9U2VhcmNoRmllbGRgO1xuXG4vLyAgIGlmICghZW51bXM/LltvcmRlckJ5RW51bUlkXSkge1xuLy8gICAgIGVycm9ycy5wdXNoKHtcbi8vICAgICAgIGZpZWxkOiBcImVudW1zXCIsXG4vLyAgICAgICBtZXNzYWdlOiBgJHtvcmRlckJ5RW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0pYCxcbi8vICAgICB9KTtcbi8vICAgfVxuLy8gICBpZiAoIWVudW1zPy5bc2VhcmNoRmllbGRFbnVtSWRdKSB7XG4vLyAgICAgZXJyb3JzLnB1c2goe1xuLy8gICAgICAgZmllbGQ6IFwiZW51bXNcIixcbi8vICAgICAgIG1lc3NhZ2U6IGAke3NlYXJjaEZpZWxkRW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkXCI6IFwiSURcIiB9KWAsXG4vLyAgICAgfSk7XG4vLyAgIH1cblxuLy8gICAvLyAzLiBlbnVtIHByb3DsnZggaWTqsIAgZW51bXPsl5Ag7KCV7J2Y65CY7Ja0IOyeiOuKlOyngCDtmZXsnbggKGNyb3NzLWZpZWxkIOqygOymnSlcbi8vICAgZm9yIChjb25zdCBwcm9wIG9mIHByb3BzID8/IFtdKSB7XG4vLyAgICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtXCIgJiYgIWVudW1zPy5bcHJvcC5pZF0pIHtcbi8vICAgICAgIGVycm9ycy5wdXNoKHtcbi8vICAgICAgICAgZmllbGQ6IGBwcm9wcy4ke3Byb3AubmFtZX1gLFxuLy8gICAgICAgICBtZXNzYWdlOiBgZW51bSBpZCBcIiR7cHJvcC5pZH1cIuqwgCBlbnVtc+yXkCDsoJXsnZjrkJjslrQg7J6I7KeAIOyViuyKteuLiOuLpC5gLFxuLy8gICAgICAgfSk7XG4vLyAgICAgfVxuLy8gICB9XG5cbi8vICAgcmV0dXJuIGVycm9ycztcbi8vIH1cblxuLy8gZXhwb3J0IGNvbnN0IGFpQ2xpZW50ID0gbmV3IEFJQ2xpZW50KCk7XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsaURBQWlEO0FBQ2pELHlFQUF5RTtBQUN6RSwrQkFBK0I7QUFDL0IsdUJBQXVCO0FBQ3ZCLDJCQUEyQjtBQUMzQixXQUFXO0FBQ1gsbUJBQW1CO0FBQ25CLHFCQUFxQjtBQUNyQix3QkFBd0I7QUFDeEIsaUJBQWlCO0FBQ2pCLFlBQVk7QUFDWixxQkFBcUI7QUFDckIsbUJBQW1CO0FBQ25CLDJCQUEyQjtBQUUzQiwyQkFBMkI7QUFDM0IsbUJBQW1CO0FBQ25CLHFCQUFxQjtBQUNyQixLQUFLO0FBRUwsbUJBQW1CO0FBQ25CLG9EQUFvRDtBQUVwRCxtQkFBbUI7QUFDbkIsd0RBQXdEO0FBQ3hELE1BQU07QUFFTiwrRUFBK0U7QUFDL0UsZ0RBQWdEO0FBQ2hELGlGQUFpRjtBQUNqRixpRUFBaUU7QUFDakUsb0RBQW9EO0FBRXBELGlCQUFpQjtBQUNqQiwrQkFBK0I7QUFDL0IsK0JBQStCO0FBQy9CLCtCQUErQjtBQUMvQix1Q0FB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
34
+ 현재 픽스쳐 레코드:
35
+ ${JSON.stringify(fixtureRecords, null, 2)}
36
+
37
+ 엔티티 구조 정보:
38
+ ${JSON.stringify(entityStructures, null, 2)}
39
+
40
+ ## 픽스쳐 수정
41
+ 사용자가 픽스쳐 값 수정을 요청하면 updateFixtures 도구를 사용하여 변경사항을 적용하세요.
42
+ - fixtureId: 수정할 픽스쳐 ID (형식: "EntityId#id")
43
+ - updates: 컬럼명을 키로, 새 값을 값으로 하는 객체
44
+
45
+ 예시: "User#1" 픽스쳐의 "name" 컬럼을 "홍길동"으로 변경하려면:
46
+ updateFixtures({ updates: [{ fixtureId: "User#1", updates: { name: "홍길동" } }] })
47
+
48
+ 변경될 컬럼의 type이 relation인 경우, 관련 엔티티에도 반영되어야 할 컬럼이 있는지 확인하세요.
49
+
50
+ ## 픽스쳐 생성
51
+ 사용자가 새로운 픽스쳐 생성을 요청하면 createFixtures 도구를 사용하세요.
52
+ - entityId: 생성할 엔티티 ID
53
+ - id: 새 레코드의 ID (기존 픽스쳐와 중복되지 않는 음수 사용 권장, 예: -1, -2)
54
+ - columns: 컬럼명을 키로, 값을 값으로 하는 객체 (엔티티 구조 참고)
55
+
56
+ 예시: 새로운 User 픽스쳐를 생성하려면:
57
+ createFixtures({ fixtures: [{ entityId: "User", id: -1, columns: { name: "홍길동", email: "hong@example.com" } }] })
58
+ `;
59
+ return streamText({
60
+ model: this.model,
61
+ system: systemMessage,
62
+ messages,
63
+ tools: {
64
+ updateFixtures: tool({
65
+ description: "픽스쳐 레코드의 값을 수정합니다. 사용자가 특정 컬럼이나 값을 변경해달라고 요청할 때 사용하세요.",
66
+ inputSchema: z.object({
67
+ updates: z.array(z.object({
68
+ fixtureId: z.string().describe("수정할 픽스쳐 ID (형식: EntityId#id)"),
69
+ updates: z.record(z.string(), z.unknown()).describe("컬럼명을 키로, 새 값을 값으로 하는 객체")
70
+ }))
71
+ }),
72
+ execute: async ({ updates })=>{
73
+ // fixtureRecords를 복사하고 업데이트 적용
74
+ const updatedRecords = fixtureRecords.map((record)=>{
75
+ const update = updates.find((u)=>u.fixtureId === record.fixtureId);
76
+ if (update) {
77
+ // columns의 value를 업데이트
78
+ for (const [columnName, newValue] of Object.entries(update.updates)){
79
+ record.columns[columnName].value = newValue;
80
+ }
81
+ return record;
82
+ }
83
+ return record;
84
+ });
85
+ return {
86
+ success: true,
87
+ updatedRecords
88
+ };
89
+ }
90
+ }),
91
+ createFixtures: tool({
92
+ description: "새로운 픽스쳐 레코드를 생성합니다. 사용자가 새로운 데이터를 추가해달라고 요청할 때 사용하세요.",
93
+ inputSchema: z.object({
94
+ fixtures: z.array(z.object({
95
+ entityId: z.string().describe("생성할 엔티티 ID"),
96
+ id: z.number().describe("새 레코드의 ID (음수 권장, 예: -1, -2)"),
97
+ columns: z.record(z.string(), z.unknown()).describe("컬럼명을 키로, 값을 값으로 하는 객체")
98
+ }))
99
+ }),
100
+ execute: async ({ fixtures })=>{
101
+ const newRecords = fixtures.map((fixture)=>{
102
+ const entity = EntityManager.get(fixture.entityId);
103
+ // 엔티티 props를 기반으로 columns 구성
104
+ const columns = {};
105
+ for (const prop of entity.props){
106
+ if (prop.type === "virtual") continue;
107
+ let value = fixture.columns[prop.name] ?? null;
108
+ if (prop.name === "created_at") {
109
+ // 현재 시간으로 설정
110
+ value = new Date().toISOString();
111
+ } else if (prop.type === "relation" && (prop.relationType === "HasMany" || prop.relationType === "ManyToMany")) {
112
+ // 배열로 변환
113
+ value = Array.isArray(value) ? value : [
114
+ value
115
+ ].filter(nonNullable);
116
+ }
117
+ columns[prop.name] = {
118
+ prop,
119
+ value: value
120
+ };
121
+ }
122
+ return {
123
+ fixtureId: `${fixture.entityId}#${fixture.id}`,
124
+ entityId: fixture.entityId,
125
+ id: fixture.id,
126
+ columns,
127
+ fetchedRecords: [],
128
+ belongsRecords: [],
129
+ override: false
130
+ };
131
+ });
132
+ // 새 레코드들의 relation 컬럼을 확인하여 기존 레코드들의 역방향 relation 업데이트
133
+ for (const newRecord of newRecords){
134
+ for (const [_colName, col] of Object.entries(newRecord.columns)){
135
+ if (col.prop.type !== "relation" || col.value === null) continue;
136
+ const relatedEntityId = col.prop.with;
137
+ const relatedIds = Array.isArray(col.value) ? col.value : [
138
+ col.value
139
+ ];
140
+ for (const relatedId of relatedIds){
141
+ const relatedFixtureId = `${relatedEntityId}#${relatedId}`;
142
+ const relatedRecord = newRecords.find((r)=>r.fixtureId === relatedFixtureId);
143
+ if (relatedRecord) {
144
+ // 역방향 relation 찾기
145
+ const reverseCol = Object.entries(relatedRecord.columns).find(([, c])=>c.prop.type === "relation" && c.prop.with === newRecord.entityId);
146
+ if (reverseCol) {
147
+ const [reverseColName, reverseColValue] = reverseCol;
148
+ const currentValue = reverseColValue.value;
149
+ // 역방향이 배열인 경우 (HasMany, ManyToMany)
150
+ if (reverseColValue.prop.type === "relation" && (reverseColValue.prop.relationType === "HasMany" || reverseColValue.prop.relationType === "ManyToMany")) {
151
+ assert(Array.isArray(currentValue), "currentValue must be an array");
152
+ if (!currentValue.includes(newRecord.id)) {
153
+ relatedRecord.columns[reverseColName] = {
154
+ ...reverseColValue,
155
+ value: [
156
+ ...currentValue,
157
+ newRecord.id
158
+ ]
159
+ };
160
+ }
161
+ } else {
162
+ // 역방향이 단일 값인 경우 (BelongsToOne, OneToOne)
163
+ relatedRecord.columns[reverseColName] = {
164
+ ...reverseColValue,
165
+ value: newRecord.id
166
+ };
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ return {
174
+ success: true,
175
+ updatedRecords: newRecords
176
+ };
177
+ }
178
+ })
179
+ }
180
+ });
181
+ }
182
+ handleEntity(messages) {
183
+ // entity.instructions.md 파일 읽기 (dist/ui 또는 src/ui에서 실행되므로 패키지 루트 기준으로 접근)
184
+ const instructionsPath = path.join(import.meta.dirname, "..", "..", "src", "ui", "entity.instructions.md");
185
+ const instructions = fs.readFileSync(instructionsPath, "utf-8");
186
+ // 현재 등록된 엔티티 정보 수집
187
+ const entityIds = EntityManager.getAllIds();
188
+ const existingEntities = entityIds.map((entityId)=>{
189
+ const entity = EntityManager.get(entityId);
190
+ return {
191
+ id: entity.id,
192
+ title: entity.title,
193
+ table: entity.table,
194
+ props: entity.props.map((p)=>({
195
+ name: p.name,
196
+ type: p.type,
197
+ desc: p.desc
198
+ }))
199
+ };
200
+ });
201
+ const systemMessage = `
202
+ 당신은 Sonamu 프레임워크에서 Entity와 Enum을 생성하는 도우미입니다.
203
+
204
+ ${instructions}
205
+
206
+ ## 현재 등록된 Entity 목록
207
+ 다른 엔티티와 관계(relation)를 맺거나 subset에서 참조할 때 반드시 아래 정보를 확인하세요.
208
+
209
+ ${JSON.stringify(existingEntities, null, 2)}
210
+
211
+ ## Tool 사용 가이드
212
+
213
+ ### Entity 생성 (createEntity)
214
+ 사용자가 새로운 Entity 생성을 요청하면 createEntity 도구를 사용하세요.
215
+ - entityId: PascalCase로 된 Entity ID (예: "User", "ProductCategory")
216
+ - title: 한글 제목 (예: "사용자", "상품 카테고리")
217
+ - table: snake_case로 된 테이블명 (예: "users", "product_categories")
218
+ - parentId: 부모 Entity ID (선택사항)
219
+ - props: Entity의 프로퍼티 배열 (위 문서의 Property Types 참고)
220
+ - indexes: 인덱스 배열
221
+ - subsets: 서브셋 정의 (기본값: { A: ["id"] })
222
+ - enums: Enum 정의
223
+
224
+ ### Entity 수정 (updateEntity)
225
+ 기존 Entity를 수정할 때 updateEntity 도구를 사용하세요. Enum 추가, props 추가/수정, indexes 수정 등 모든 수정 작업에 사용합니다.
226
+ - entityId: 수정할 Entity ID
227
+ - updates: 수정할 필드들 (부분 업데이트)
228
+ - title: 엔티티 한글 제목
229
+ - table: 테이블명
230
+ - props: 추가할 프로퍼티 배열 (기존 props에 추가, 같은 이름이면 교체)
231
+ - indexes: 추가할 인덱스 배열 (기존 indexes에 추가)
232
+ - subsets: 서브셋 정의 (기존 subsets에 병합)
233
+ - enumLabels: Enum 정의 (기존 enumLabels에 병합)
234
+ - mode: "merge"(기본값) 또는 "replace"
235
+ - merge: 기존 값에 병합
236
+ - replace: 해당 필드 전체 교체
237
+
238
+ 예시: Employee에 새 Enum 추가
239
+ updateEntity({ entityId: "Employee", updates: { enumLabels: { "EmployeeRole": { "admin": "관리자", "user": "일반" } } } })
240
+
241
+ 예시: Project에 새 프로퍼티 추가
242
+ updateEntity({ entityId: "Project", updates: { props: [{ name: "priority", type: "integer", desc: "우선순위" }] } })
243
+
244
+ ## 필수 사항
245
+ - Entity의 props에는 최소한 id(integer, unsigned), created_at(timestamp)가 포함되어야 합니다.
246
+ - relation 필드는 onUpdate, onDelete가 필수입니다. (예외: OneToOne에서 hasJoinColumn이 false인 경우)
247
+ - Enum ID는 보통 EntityId + 속성명 형태입니다 (예: UserStatus, ProductType)
248
+ - subset에서 다른 엔티티의 프로퍼티를 참조할 때는 반드시 해당 엔티티의 실제 프로퍼티명을 사용하세요.
249
+
250
+ ## 검증 오류 처리
251
+ 도구 호출 결과로 검증 오류(validationErrors)가 반환되면:
252
+ 1. 오류 메시지를 분석하여 문제점을 파악하세요.
253
+ 2. 오류를 수정한 데이터로 createEntity를 다시 호출하세요.
254
+ 3. 사용자에게 오류를 그대로 전달하지 말고, 수정 후 재시도하세요.
255
+
256
+ ### 일반적인 검증 오류와 수정 방법
257
+ | 오류 메시지 | 수정 방법 |
258
+ |------------|----------|
259
+ | "id 프로퍼티가 필수" | props에 { name: "id", type: "integer", unsigned: true } 추가 |
260
+ | "created_at 프로퍼티가 필수" | props에 { name: "created_at", type: "timestamp", dbDefault: "CURRENT_TIMESTAMP" } 추가 |
261
+ | "XxxOrderBy enum이 필수" | enums에 { "XxxOrderBy": { "id-desc": "ID최신순" } } 추가 |
262
+ | "XxxSearchField enum이 필수" | enums에 { "XxxSearchField": { "id": "ID" } } 추가 |
263
+ | "string 타입은 length가 필수" | 해당 prop에 length 추가 (예: 255) |
264
+ | "text 타입은 textType이 필수" | 해당 prop에 textType 추가 ("text", "mediumtext", "longtext") |
265
+ | "onUpdate가 필수" | 해당 relation prop에 onUpdate, onDelete 추가 ("CASCADE") |
266
+ `;
267
+ return streamText({
268
+ model: this.model,
269
+ system: systemMessage,
270
+ messages,
271
+ stopWhen: stepCountIs(2),
272
+ tools: {
273
+ createEntity: tool({
274
+ description: "새로운 Entity를 생성합니다. 사용자가 새로운 엔티티나 테이블 생성을 요청할 때 사용하세요.",
275
+ inputSchema: TemplateOptions.shape.entity,
276
+ execute: async (entity)=>{
277
+ try {
278
+ // 입력 검증
279
+ const validationErrors = validateEntityJson(entity);
280
+ if (validationErrors.length > 0) {
281
+ return {
282
+ success: false,
283
+ entityId: entity.entityId,
284
+ error: `검증 오류: ${validationErrors.map((e)=>`[${e.field}] ${e.message}`).join(", ")}`,
285
+ validationErrors
286
+ };
287
+ }
288
+ await Sonamu.syncer.createEntity({
289
+ subsets: {
290
+ A: [
291
+ "id"
292
+ ]
293
+ },
294
+ enums: {},
295
+ ...entity
296
+ });
297
+ // EntityManager 리로드
298
+ await EntityManager.reload();
299
+ return {
300
+ success: true,
301
+ entityId: entity.entityId
302
+ };
303
+ } catch (e) {
304
+ const error = e instanceof Error ? e.message : "Unknown error";
305
+ return {
306
+ success: false,
307
+ entityId: entity.entityId,
308
+ error
309
+ };
310
+ }
311
+ }
312
+ }),
313
+ updateEntity: tool({
314
+ description: "기존 Entity를 수정합니다. Enum 추가, props 추가/수정, indexes 수정, subsets 수정 등 모든 엔티티 수정 작업에 사용하세요.",
315
+ inputSchema: z.object({
316
+ entityId: z.string().describe("수정할 Entity ID"),
317
+ updates: TemplateOptions.shape.entity.partial().describe("수정할 필드들"),
318
+ mode: z.enum([
319
+ "merge",
320
+ "replace"
321
+ ]).optional().describe("수정 모드: merge(기본값, 기존 값에 병합) 또는 replace(전체 교체)")
322
+ }),
323
+ execute: async ({ entityId, updates, mode = "merge" })=>{
324
+ try {
325
+ const entity = EntityManager.get(entityId);
326
+ // Update basic properties
327
+ if (updates.entityId !== undefined) entity.id = updates.entityId;
328
+ if (updates.parentId !== undefined) entity.parentId = updates.parentId;
329
+ if (updates.title !== undefined) entity.title = updates.title;
330
+ if (updates.table !== undefined) entity.table = updates.table;
331
+ // props: merge 시 이름 기준 병합, replace 시 교체
332
+ if (updates.props !== undefined) {
333
+ if (mode === "replace") {
334
+ entity.props = updates.props;
335
+ } else {
336
+ for (const newProp of updates.props){
337
+ const existingIndex = entity.props.findIndex((p)=>p.name === newProp.name);
338
+ if (existingIndex >= 0) {
339
+ entity.props[existingIndex] = newProp;
340
+ } else {
341
+ entity.props.push(newProp);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ // indexes: merge 시 추가, replace 시 교체
347
+ if (updates.indexes !== undefined) {
348
+ entity.indexes = mode === "replace" ? updates.indexes : [
349
+ ...entity.indexes,
350
+ ...updates.indexes
351
+ ];
352
+ }
353
+ // subsets, subsetsInternal: assign으로 병합 또는 교체
354
+ if (updates.subsets !== undefined) {
355
+ // Normalize subset fields: separate into subsets (normal) and subsetsInternal (internal)
356
+ const normalizedSubsets = {};
357
+ const normalizedSubsetsInternal = {};
358
+ for (const [key, fields] of Object.entries(updates.subsets)){
359
+ normalizedSubsets[key] = fields.filter((f)=>!isInternalSubsetField(f)).map(normalizeSubsetField);
360
+ normalizedSubsetsInternal[key] = fields.filter(isInternalSubsetField).map(normalizeSubsetField);
361
+ }
362
+ entity.subsets = mode === "replace" ? normalizedSubsets : {
363
+ ...entity.subsets,
364
+ ...normalizedSubsets
365
+ };
366
+ entity.subsetsInternal = mode === "replace" ? normalizedSubsetsInternal : {
367
+ ...entity.subsetsInternal,
368
+ ...normalizedSubsetsInternal
369
+ };
370
+ }
371
+ if (updates.enums !== undefined) {
372
+ entity.enumLabels = mode === "replace" ? updates.enums : {
373
+ ...entity.enumLabels,
374
+ ...updates.enums
375
+ };
376
+ }
377
+ // 저장 전 검증
378
+ const validationErrors = validateEntityJson({
379
+ ...entity,
380
+ entityId: entity.id,
381
+ enums: entity.enumLabels
382
+ });
383
+ if (validationErrors.length > 0) {
384
+ return {
385
+ success: false,
386
+ entityId,
387
+ error: `검증 오류: ${validationErrors.map((e)=>`[${e.field}] ${e.message}`).join(", ")}`,
388
+ validationErrors
389
+ };
390
+ }
391
+ await entity.save();
392
+ return {
393
+ success: true,
394
+ entityId
395
+ };
396
+ } catch (e) {
397
+ const error = e instanceof Error ? e.message : "Unknown error";
398
+ return {
399
+ success: false,
400
+ entityId,
401
+ error
402
+ };
403
+ }
404
+ }
405
+ })
406
+ }
407
+ });
408
+ }
409
+ }
410
+ /**
411
+ * Entity JSON이 entity.instructions.md의 규칙을 따르는지 검증합니다.
412
+ */ function validateEntityJson(input) {
413
+ const errors = [];
414
+ const { entityId, props, enums } = input;
415
+ // 1. id, created_at prop 필수
416
+ const hasIdProp = props?.some((p)=>p.name === "id");
417
+ if (!hasIdProp) {
418
+ errors.push({
419
+ field: "props",
420
+ message: "id 프로퍼티가 필수입니다."
421
+ });
422
+ }
423
+ const hasCreatedAtProp = props?.some((p)=>p.name === "created_at");
424
+ if (!hasCreatedAtProp) {
425
+ errors.push({
426
+ field: "props",
427
+ message: "created_at 프로퍼티가 필수입니다."
428
+ });
429
+ }
430
+ // 2. 필수 enum 검증: EntityNameOrderBy, EntityNameSearchField
431
+ const orderByEnumId = `${entityId}OrderBy`;
432
+ const searchFieldEnumId = `${entityId}SearchField`;
433
+ if (!enums?.[orderByEnumId]) {
434
+ errors.push({
435
+ field: "enums",
436
+ message: `${orderByEnumId} enum이 필수입니다. (예: { "id-desc": "ID최신순" })`
437
+ });
438
+ }
439
+ if (!enums?.[searchFieldEnumId]) {
440
+ errors.push({
441
+ field: "enums",
442
+ message: `${searchFieldEnumId} enum이 필수입니다. (예: { "id": "ID" })`
443
+ });
444
+ }
445
+ // 3. enum prop의 id가 enums에 정의되어 있는지 확인 (cross-field 검증)
446
+ for (const prop of props ?? []){
447
+ if (prop.type === "enum" && !enums?.[prop.id]) {
448
+ errors.push({
449
+ field: `props.${prop.name}`,
450
+ message: `enum id "${prop.id}"가 enums에 정의되어 있지 않습니다.`
451
+ });
452
+ }
453
+ }
454
+ return errors;
455
+ }
456
+ export const aiClient = new AIClient();
457
+
458
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91aS9haS1jbGllbnQudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqIGJpb21lLWlnbm9yZS1hbGwgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IEFJIFNES+ydmCDtg4DsnoXsnbQg66qF7ZmV7ZWY7KeAIOyViuyVhCBhbnnrpbwg7ZeI7Jqp7ZWoICovXG5pbXBvcnQgeyBhbnRocm9waWMgfSBmcm9tIFwiQGFpLXNkay9hbnRocm9waWNcIjtcbmltcG9ydCB7XG4gIHR5cGUgTGFuZ3VhZ2VNb2RlbCxcbiAgdHlwZSBNb2RlbE1lc3NhZ2UsXG4gIHR5cGUgU3RyZWFtVGV4dFJlc3VsdCxcbiAgc3RlcENvdW50SXMsXG4gIHN0cmVhbVRleHQsXG4gIHRvb2wsXG59IGZyb20gXCJhaVwiO1xuaW1wb3J0IGFzc2VydCBmcm9tIFwiYXNzZXJ0XCI7XG5pbXBvcnQgZnMgZnJvbSBcImZzXCI7XG5pbXBvcnQgcGF0aCBmcm9tIFwicGF0aFwiO1xuaW1wb3J0IHsgeiB9IGZyb20gXCJ6b2RcIjtcbmltcG9ydCB7IFNvbmFtdSB9IGZyb20gXCIuLi9hcGlcIjtcbmltcG9ydCB7IEVudGl0eU1hbmFnZXIgfSBmcm9tIFwiLi4vZW50aXR5L2VudGl0eS1tYW5hZ2VyXCI7XG5pbXBvcnQge1xuICB0eXBlIEVudGl0eVByb3AsXG4gIHR5cGUgRml4dHVyZVJlY29yZCxcbiAgaXNJbnRlcm5hbFN1YnNldEZpZWxkLFxuICBub3JtYWxpemVTdWJzZXRGaWVsZCxcbiAgVGVtcGxhdGVPcHRpb25zLFxufSBmcm9tIFwiLi4vdHlwZXMvdHlwZXNcIjtcbmltcG9ydCB7IG5vbk51bGxhYmxlIH0gZnJvbSBcIi4uL3V0aWxzL3V0aWxzXCI7XG5cbnR5cGUgVmFsaWRhdGlvbkVycm9yID0ge1xuICBmaWVsZDogc3RyaW5nO1xuICBtZXNzYWdlOiBzdHJpbmc7XG59O1xuXG5jbGFzcyBBSUNsaWVudCB7XG4gIHByaXZhdGUgbW9kZWwgPSBhbnRocm9waWMoXCJjbGF1ZGUtc29ubmV0LTQtNVwiKTtcblxuICBhc3luYyBpbml0KCkge1xuICAgIGNvbnNvbGUubG9nKFwiQUkgY2xpZW50IGluaXRpYWxpemVkIHdpdGggQUkgU0RLXCIpO1xuICB9XG5cbiAgaGFuZGxlRml4dHVyZShcbiAgICBtZXNzYWdlczogTW9kZWxNZXNzYWdlW10sXG4gICAgZml4dHVyZVJlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSxcbiAgKTogU3RyZWFtVGV4dFJlc3VsdDxhbnksIGFueT4ge1xuICAgIC8vIO2YhOyerCBmaXh0dXJlUmVjb3Jkc+yXkOyEnCDsgqzsmqnrkJwg7JeU7Yuw7Yuw65Ok7J2YIOq1rOyhsCDsoJXrs7Qg7IiY7KeRXG4gICAgY29uc3QgdXNlZEVudGl0eUlkcyA9IFsuLi5uZXcgU2V0KGZpeHR1cmVSZWNvcmRzLm1hcCgocikgPT4gci5lbnRpdHlJZCkpXTtcbiAgICBjb25zdCBlbnRpdHlTdHJ1Y3R1cmVzID0gdXNlZEVudGl0eUlkcy5tYXAoKGVudGl0eUlkKSA9PiB7XG4gICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4gICAgICAgIHRhYmxlOiBlbnRpdHkudGFibGUsXG4gICAgICAgIHByb3BzOiBlbnRpdHkucHJvcHMsXG4gICAgICAgIHJlbGF0aW9uczogZW50aXR5LnJlbGF0aW9ucyxcbiAgICAgICAgZW51bUxhYmVsczogZW50aXR5LmVudW1MYWJlbHMsXG4gICAgICB9O1xuICAgIH0pO1xuXG4gICAgY29uc3Qgc3lzdGVtTWVzc2FnZSA9IGBcbiAgICAgICAg64u57Iug7J2AIO2UveyKpOyzkCDroIjsvZTrk5zrpbwg7IiY7KCV7ZWY6rOgIOyDneyEse2VoCDsiJgg7J6I64qUIOuPhOyasOuvuOyeheuLiOuLpC5cblxuICAgICAgICDtmITsnqwg7ZS97Iqk7LOQIOugiOy9lOuTnDpcbiAgICAgICAgJHtKU09OLnN0cmluZ2lmeShmaXh0dXJlUmVjb3JkcywgbnVsbCwgMil9XG5cbiAgICAgICAg7JeU7Yuw7YuwIOq1rOyhsCDsoJXrs7Q6XG4gICAgICAgICR7SlNPTi5zdHJpbmdpZnkoZW50aXR5U3RydWN0dXJlcywgbnVsbCwgMil9XG5cbiAgICAgICAgIyMg7ZS97Iqk7LOQIOyImOyglVxuICAgICAgICDsgqzsmqnsnpDqsIAg7ZS97Iqk7LOQIOqwkiDsiJjsoJXsnYQg7JqU7LKt7ZWY66m0IHVwZGF0ZUZpeHR1cmVzIOuPhOq1rOulvCDsgqzsmqntlZjsl6wg67OA6rK97IKs7ZWt7J2EIOyggeyaqe2VmOyEuOyalC5cbiAgICAgICAgLSBmaXh0dXJlSWQ6IOyImOygle2VoCDtlL3siqTss5AgSUQgKO2YleyLnTogXCJFbnRpdHlJZCNpZFwiKVxuICAgICAgICAtIHVwZGF0ZXM6IOy7rOufvOuqheydhCDtgqTroZwsIOyDiCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcblxuICAgICAgICDsmIjsi5w6IFwiVXNlciMxXCIg7ZS97Iqk7LOQ7J2YIFwibmFtZVwiIOy7rOufvOydhCBcIu2Zjeq4uOuPmVwi7Jy866GcIOuzgOqyve2VmOugpOuptDpcbiAgICAgICAgdXBkYXRlRml4dHVyZXMoeyB1cGRhdGVzOiBbeyBmaXh0dXJlSWQ6IFwiVXNlciMxXCIsIHVwZGF0ZXM6IHsgbmFtZTogXCLtmY3quLjrj5lcIiB9IH1dIH0pXG5cbiAgICAgICAg67OA6rK965CgIOy7rOufvOydmCB0eXBl7J20IHJlbGF0aW9u7J24IOqyveyasCwg6rSA66CoIOyXlO2LsO2LsOyXkOuPhCDrsJjsmIHrkJjslrTslbwg7ZWgIOy7rOufvOydtCDsnojripTsp4Ag7ZmV7J247ZWY7IS47JqULlxuXG4gICAgICAgICMjIO2UveyKpOyzkCDsg53shLFcbiAgICAgICAg7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDtlL3siqTss5Ag7IOd7ISx7J2EIOyalOyyre2VmOuptCBjcmVhdGVGaXh0dXJlcyDrj4Tqtazrpbwg7IKs7Jqp7ZWY7IS47JqULlxuICAgICAgICAtIGVudGl0eUlkOiDsg53shLHtlaAg7JeU7Yuw7YuwIElEXG4gICAgICAgIC0gaWQ6IOyDiCDroIjsvZTrk5zsnZggSUQgKOq4sOyhtCDtlL3siqTss5DsmYAg7KSR67O165CY7KeAIOyViuuKlCDsnYzsiJgg7IKs7JqpIOq2jOyepSwg7JiIOiAtMSwgLTIpXG4gICAgICAgIC0gY29sdW1uczog7Lus65+866qF7J2EIO2CpOuhnCwg6rCS7J2EIOqwkuycvOuhnCDtlZjripQg6rCd7LK0ICjsl5Tti7Dti7Ag6rWs7KGwIOywuOqzoClcblxuICAgICAgICDsmIjsi5w6IOyDiOuhnOyatCBVc2VyIO2UveyKpOyzkOulvCDsg53shLHtlZjroKTrqbQ6XG4gICAgICAgIGNyZWF0ZUZpeHR1cmVzKHsgZml4dHVyZXM6IFt7IGVudGl0eUlkOiBcIlVzZXJcIiwgaWQ6IC0xLCBjb2x1bW5zOiB7IG5hbWU6IFwi7ZmN6ri464+ZXCIsIGVtYWlsOiBcImhvbmdAZXhhbXBsZS5jb21cIiB9IH1dIH0pXG4gICAgICBgO1xuXG4gICAgcmV0dXJuIHN0cmVhbVRleHQoe1xuICAgICAgbW9kZWw6IHRoaXMubW9kZWwsXG4gICAgICBzeXN0ZW06IHN5c3RlbU1lc3NhZ2UsXG4gICAgICBtZXNzYWdlcyxcbiAgICAgIHRvb2xzOiB7XG4gICAgICAgIHVwZGF0ZUZpeHR1cmVzOiB0b29sKHtcbiAgICAgICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgICAgIFwi7ZS97Iqk7LOQIOugiOy9lOuTnOydmCDqsJLsnYQg7IiY7KCV7ZWp64uI64ukLiDsgqzsmqnsnpDqsIAg7Yq57KCVIOy7rOufvOydtOuCmCDqsJLsnYQg67OA6rK97ZW064us65286rOgIOyalOyyre2VoCDrlYwg7IKs7Jqp7ZWY7IS47JqULlwiLFxuICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4gICAgICAgICAgICB1cGRhdGVzOiB6LmFycmF5KFxuICAgICAgICAgICAgICB6Lm9iamVjdCh7XG4gICAgICAgICAgICAgICAgZml4dHVyZUlkOiB6LnN0cmluZygpLmRlc2NyaWJlKFwi7IiY7KCV7ZWgIO2UveyKpOyzkCBJRCAo7ZiV7IudOiBFbnRpdHlJZCNpZClcIiksXG4gICAgICAgICAgICAgICAgdXBkYXRlczogelxuICAgICAgICAgICAgICAgICAgLnJlY29yZCh6LnN0cmluZygpLCB6LnVua25vd24oKSlcbiAgICAgICAgICAgICAgICAgIC5kZXNjcmliZShcIuy7rOufvOuqheydhCDtgqTroZwsIOyDiCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcIiksXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICB9KSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoe1xuICAgICAgICAgICAgdXBkYXRlcyxcbiAgICAgICAgICB9KTogUHJvbWlzZTx7IHN1Y2Nlc3M6IGJvb2xlYW47IHVwZGF0ZWRSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gfT4gPT4ge1xuICAgICAgICAgICAgLy8gZml4dHVyZVJlY29yZHPrpbwg67O17IKs7ZWY6rOgIOyXheuNsOydtO2KuCDsoIHsmqlcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWRSZWNvcmRzOiBGaXh0dXJlUmVjb3JkW10gPSBmaXh0dXJlUmVjb3Jkcy5tYXAoKHJlY29yZCkgPT4ge1xuICAgICAgICAgICAgICBjb25zdCB1cGRhdGUgPSB1cGRhdGVzLmZpbmQoKHUpID0+IHUuZml4dHVyZUlkID09PSByZWNvcmQuZml4dHVyZUlkKTtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZSkge1xuICAgICAgICAgICAgICAgIC8vIGNvbHVtbnPsnZggdmFsdWXrpbwg7JeF642w7J207Yq4XG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBbY29sdW1uTmFtZSwgbmV3VmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHVwZGF0ZS51cGRhdGVzKSkge1xuICAgICAgICAgICAgICAgICAgcmVjb3JkLmNvbHVtbnNbY29sdW1uTmFtZV0udmFsdWUgPVxuICAgICAgICAgICAgICAgICAgICBuZXdWYWx1ZSBhcyBGaXh0dXJlUmVjb3JkW1wiY29sdW1uc1wiXVtzdHJpbmddW1widmFsdWVcIl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHJldHVybiByZWNvcmQ7XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICByZXR1cm4gcmVjb3JkO1xuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIHVwZGF0ZWRSZWNvcmRzIH07XG4gICAgICAgICAgfSxcbiAgICAgICAgfSksXG4gICAgICAgIGNyZWF0ZUZpeHR1cmVzOiB0b29sKHtcbiAgICAgICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgICAgIFwi7IOI66Gc7Jq0IO2UveyKpOyzkCDroIjsvZTrk5zrpbwg7IOd7ISx7ZWp64uI64ukLiDsgqzsmqnsnpDqsIAg7IOI66Gc7Jq0IOuNsOydtO2EsOulvCDstpTqsIDtlbTri6zrnbzqs6Ag7JqU7LKt7ZWgIOuVjCDsgqzsmqntlZjshLjsmpQuXCIsXG4gICAgICAgICAgaW5wdXRTY2hlbWE6IHoub2JqZWN0KHtcbiAgICAgICAgICAgIGZpeHR1cmVzOiB6LmFycmF5KFxuICAgICAgICAgICAgICB6Lm9iamVjdCh7XG4gICAgICAgICAgICAgICAgZW50aXR5SWQ6IHouc3RyaW5nKCkuZGVzY3JpYmUoXCLsg53shLHtlaAg7JeU7Yuw7YuwIElEXCIpLFxuICAgICAgICAgICAgICAgIGlkOiB6Lm51bWJlcigpLmRlc2NyaWJlKFwi7IOIIOugiOy9lOuTnOydmCBJRCAo7J2M7IiYIOq2jOyepSwg7JiIOiAtMSwgLTIpXCIpLFxuICAgICAgICAgICAgICAgIGNvbHVtbnM6IHpcbiAgICAgICAgICAgICAgICAgIC5yZWNvcmQoei5zdHJpbmcoKSwgei51bmtub3duKCkpXG4gICAgICAgICAgICAgICAgICAuZGVzY3JpYmUoXCLsu6zrn7zrqoXsnYQg7YKk66GcLCDqsJLsnYQg6rCS7Jy866GcIO2VmOuKlCDqsJ3ssrRcIiksXG4gICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICB9KSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoe1xuICAgICAgICAgICAgZml4dHVyZXMsXG4gICAgICAgICAgfSk6IFByb21pc2U8eyBzdWNjZXNzOiBib29sZWFuOyB1cGRhdGVkUmVjb3JkczogRml4dHVyZVJlY29yZFtdIH0+ID0+IHtcbiAgICAgICAgICAgIGNvbnN0IG5ld1JlY29yZHM6IEZpeHR1cmVSZWNvcmRbXSA9IGZpeHR1cmVzLm1hcCgoZml4dHVyZSkgPT4ge1xuICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChmaXh0dXJlLmVudGl0eUlkKTtcblxuICAgICAgICAgICAgICAvLyDsl5Tti7Dti7AgcHJvcHPrpbwg6riw67CY7Jy866GcIGNvbHVtbnMg6rWs7ISxXG4gICAgICAgICAgICAgIGNvbnN0IGNvbHVtbnM6IEZpeHR1cmVSZWNvcmRbXCJjb2x1bW5zXCJdID0ge307XG4gICAgICAgICAgICAgIGZvciAoY29uc3QgcHJvcCBvZiBlbnRpdHkucHJvcHMpIHtcbiAgICAgICAgICAgICAgICBpZiAocHJvcC50eXBlID09PSBcInZpcnR1YWxcIikgY29udGludWU7XG5cbiAgICAgICAgICAgICAgICBsZXQgdmFsdWUgPSBmaXh0dXJlLmNvbHVtbnNbcHJvcC5uYW1lXSA/PyBudWxsO1xuXG4gICAgICAgICAgICAgICAgaWYgKHByb3AubmFtZSA9PT0gXCJjcmVhdGVkX2F0XCIpIHtcbiAgICAgICAgICAgICAgICAgIC8vIO2YhOyerCDsi5zqsITsnLzroZwg7ISk7KCVXG4gICAgICAgICAgICAgICAgICB2YWx1ZSA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKFxuICAgICAgICAgICAgICAgICAgcHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiZcbiAgICAgICAgICAgICAgICAgIChwcm9wLnJlbGF0aW9uVHlwZSA9PT0gXCJIYXNNYW55XCIgfHwgcHJvcC5yZWxhdGlvblR5cGUgPT09IFwiTWFueVRvTWFueVwiKVxuICAgICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICAgLy8g67Cw7Je066GcIOuzgO2ZmFxuICAgICAgICAgICAgICAgICAgdmFsdWUgPSBBcnJheS5pc0FycmF5KHZhbHVlKSA/IHZhbHVlIDogW3ZhbHVlXS5maWx0ZXIobm9uTnVsbGFibGUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNvbHVtbnNbcHJvcC5uYW1lXSA9IHtcbiAgICAgICAgICAgICAgICAgIHByb3AsXG4gICAgICAgICAgICAgICAgICB2YWx1ZTogdmFsdWUgYXMgRml4dHVyZVJlY29yZFtcImNvbHVtbnNcIl1bc3RyaW5nXVtcInZhbHVlXCJdLFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIGZpeHR1cmVJZDogYCR7Zml4dHVyZS5lbnRpdHlJZH0jJHtmaXh0dXJlLmlkfWAsXG4gICAgICAgICAgICAgICAgZW50aXR5SWQ6IGZpeHR1cmUuZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgaWQ6IGZpeHR1cmUuaWQsXG4gICAgICAgICAgICAgICAgY29sdW1ucyxcbiAgICAgICAgICAgICAgICBmZXRjaGVkUmVjb3JkczogW10sXG4gICAgICAgICAgICAgICAgYmVsb25nc1JlY29yZHM6IFtdLFxuICAgICAgICAgICAgICAgIG92ZXJyaWRlOiBmYWxzZSxcbiAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvLyDsg4gg66CI7L2U65Oc65Ok7J2YIHJlbGF0aW9uIOy7rOufvOydhCDtmZXsnbjtlZjsl6wg6riw7KG0IOugiOy9lOuTnOuTpOydmCDsl63rsKntlqUgcmVsYXRpb24g7JeF642w7J207Yq4XG4gICAgICAgICAgICBmb3IgKGNvbnN0IG5ld1JlY29yZCBvZiBuZXdSZWNvcmRzKSB7XG4gICAgICAgICAgICAgIGZvciAoY29uc3QgW19jb2xOYW1lLCBjb2xdIG9mIE9iamVjdC5lbnRyaWVzKG5ld1JlY29yZC5jb2x1bW5zKSkge1xuICAgICAgICAgICAgICAgIGlmIChjb2wucHJvcC50eXBlICE9PSBcInJlbGF0aW9uXCIgfHwgY29sLnZhbHVlID09PSBudWxsKSBjb250aW51ZTtcblxuICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRFbnRpdHlJZCA9IGNvbC5wcm9wLndpdGg7XG4gICAgICAgICAgICAgICAgY29uc3QgcmVsYXRlZElkcyA9IEFycmF5LmlzQXJyYXkoY29sLnZhbHVlKSA/IGNvbC52YWx1ZSA6IFtjb2wudmFsdWVdO1xuXG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCByZWxhdGVkSWQgb2YgcmVsYXRlZElkcykge1xuICAgICAgICAgICAgICAgICAgY29uc3QgcmVsYXRlZEZpeHR1cmVJZCA9IGAke3JlbGF0ZWRFbnRpdHlJZH0jJHtyZWxhdGVkSWR9YDtcbiAgICAgICAgICAgICAgICAgIGNvbnN0IHJlbGF0ZWRSZWNvcmQgPSBuZXdSZWNvcmRzLmZpbmQoKHIpID0+IHIuZml4dHVyZUlkID09PSByZWxhdGVkRml4dHVyZUlkKTtcblxuICAgICAgICAgICAgICAgICAgaWYgKHJlbGF0ZWRSZWNvcmQpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7ZalIHJlbGF0aW9uIOywvuq4sFxuICAgICAgICAgICAgICAgICAgICBjb25zdCByZXZlcnNlQ29sID0gT2JqZWN0LmVudHJpZXMocmVsYXRlZFJlY29yZC5jb2x1bW5zKS5maW5kKFxuICAgICAgICAgICAgICAgICAgICAgIChbLCBjXSkgPT4gYy5wcm9wLnR5cGUgPT09IFwicmVsYXRpb25cIiAmJiBjLnByb3Aud2l0aCA9PT0gbmV3UmVjb3JkLmVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXZlcnNlQ29sKSB7XG4gICAgICAgICAgICAgICAgICAgICAgY29uc3QgW3JldmVyc2VDb2xOYW1lLCByZXZlcnNlQ29sVmFsdWVdID0gcmV2ZXJzZUNvbDtcbiAgICAgICAgICAgICAgICAgICAgICBjb25zdCBjdXJyZW50VmFsdWUgPSByZXZlcnNlQ29sVmFsdWUudmFsdWU7XG5cbiAgICAgICAgICAgICAgICAgICAgICAvLyDsl63rsKntlqXsnbQg67Cw7Je07J24IOqyveyasCAoSGFzTWFueSwgTWFueVRvTWFueSlcbiAgICAgICAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgICAgICByZXZlcnNlQ29sVmFsdWUucHJvcC50eXBlID09PSBcInJlbGF0aW9uXCIgJiZcbiAgICAgICAgICAgICAgICAgICAgICAgIChyZXZlcnNlQ29sVmFsdWUucHJvcC5yZWxhdGlvblR5cGUgPT09IFwiSGFzTWFueVwiIHx8XG4gICAgICAgICAgICAgICAgICAgICAgICAgIHJldmVyc2VDb2xWYWx1ZS5wcm9wLnJlbGF0aW9uVHlwZSA9PT0gXCJNYW55VG9NYW55XCIpXG4gICAgICAgICAgICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBhc3NlcnQoQXJyYXkuaXNBcnJheShjdXJyZW50VmFsdWUpLCBcImN1cnJlbnRWYWx1ZSBtdXN0IGJlIGFuIGFycmF5XCIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFjdXJyZW50VmFsdWUuaW5jbHVkZXMobmV3UmVjb3JkLmlkKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICByZWxhdGVkUmVjb3JkLmNvbHVtbnNbcmV2ZXJzZUNvbE5hbWVdID0ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLnJldmVyc2VDb2xWYWx1ZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZTogWy4uLmN1cnJlbnRWYWx1ZSwgbmV3UmVjb3JkLmlkXSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8g7Jet67Cp7Zal7J20IOuLqOydvCDqsJLsnbgg6rK97JqwIChCZWxvbmdzVG9PbmUsIE9uZVRvT25lKVxuICAgICAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFJlY29yZC5jb2x1bW5zW3JldmVyc2VDb2xOYW1lXSA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgLi4ucmV2ZXJzZUNvbFZhbHVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZTogbmV3UmVjb3JkLmlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogdHJ1ZSwgdXBkYXRlZFJlY29yZHM6IG5ld1JlY29yZHMgfTtcbiAgICAgICAgICB9LFxuICAgICAgICB9KSxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cblxuICBoYW5kbGVFbnRpdHkobWVzc2FnZXM6IE1vZGVsTWVzc2FnZVtdKTogU3RyZWFtVGV4dFJlc3VsdDxhbnksIGFueT4ge1xuICAgIC8vIGVudGl0eS5pbnN0cnVjdGlvbnMubWQg7YyM7J28IOydveq4sCAoZGlzdC91aSDrmJDripQgc3JjL3Vp7JeQ7IScIOyLpO2WieuQmOuvgOuhnCDtjKjtgqTsp4Ag66Oo7Yq4IOq4sOykgOycvOuhnCDsoJHqt7wpXG4gICAgY29uc3QgaW5zdHJ1Y3Rpb25zUGF0aCA9IHBhdGguam9pbihcbiAgICAgIGltcG9ydC5tZXRhLmRpcm5hbWUsXG4gICAgICBcIi4uXCIsXG4gICAgICBcIi4uXCIsXG4gICAgICBcInNyY1wiLFxuICAgICAgXCJ1aVwiLFxuICAgICAgXCJlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1kXCIsXG4gICAgKTtcbiAgICBjb25zdCBpbnN0cnVjdGlvbnMgPSBmcy5yZWFkRmlsZVN5bmMoaW5zdHJ1Y3Rpb25zUGF0aCwgXCJ1dGYtOFwiKTtcblxuICAgIC8vIO2YhOyerCDrk7HroZ3rkJwg7JeU7Yuw7YuwIOygleuztCDsiJjsp5FcbiAgICBjb25zdCBlbnRpdHlJZHMgPSBFbnRpdHlNYW5hZ2VyLmdldEFsbElkcygpO1xuICAgIGNvbnN0IGV4aXN0aW5nRW50aXRpZXMgPSBlbnRpdHlJZHMubWFwKChlbnRpdHlJZCkgPT4ge1xuICAgICAgY29uc3QgZW50aXR5ID0gRW50aXR5TWFuYWdlci5nZXQoZW50aXR5SWQpO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaWQ6IGVudGl0eS5pZCxcbiAgICAgICAgdGl0bGU6IGVudGl0eS50aXRsZSxcbiAgICAgICAgdGFibGU6IGVudGl0eS50YWJsZSxcbiAgICAgICAgcHJvcHM6IGVudGl0eS5wcm9wcy5tYXAoKHApID0+ICh7XG4gICAgICAgICAgbmFtZTogcC5uYW1lLFxuICAgICAgICAgIHR5cGU6IHAudHlwZSxcbiAgICAgICAgICBkZXNjOiBwLmRlc2MsXG4gICAgICAgIH0pKSxcbiAgICAgIH07XG4gICAgfSk7XG5cbiAgICBjb25zdCBzeXN0ZW1NZXNzYWdlID0gYFxu64u57Iug7J2AIFNvbmFtdSDtlITroIjsnoTsm4ztgazsl5DshJwgRW50aXR57JmAIEVudW3snYQg7IOd7ISx7ZWY64qUIOuPhOyasOuvuOyeheuLiOuLpC5cblxuJHtpbnN0cnVjdGlvbnN9XG5cbiMjIO2YhOyerCDrk7HroZ3rkJwgRW50aXR5IOuqqeuhnVxu64uk66W4IOyXlO2LsO2LsOyZgCDqtIDqs4QocmVsYXRpb24p66W8IOunuuqxsOuCmCBzdWJzZXTsl5DshJwg7LC47KGw7ZWgIOuVjCDrsJjrk5zsi5wg7JWE656YIOygleuztOulvCDtmZXsnbjtlZjshLjsmpQuXG5cbiR7SlNPTi5zdHJpbmdpZnkoZXhpc3RpbmdFbnRpdGllcywgbnVsbCwgMil9XG5cbiMjIFRvb2wg7IKs7JqpIOqwgOydtOuTnFxuXG4jIyMgRW50aXR5IOyDneyEsSAoY3JlYXRlRW50aXR5KVxu7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCBFbnRpdHkg7IOd7ISx7J2EIOyalOyyre2VmOuptCBjcmVhdGVFbnRpdHkg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC5cbi0gZW50aXR5SWQ6IFBhc2NhbENhc2XroZwg65CcIEVudGl0eSBJRCAo7JiIOiBcIlVzZXJcIiwgXCJQcm9kdWN0Q2F0ZWdvcnlcIilcbi0gdGl0bGU6IO2VnOq4gCDsoJzrqqkgKOyYiDogXCLsgqzsmqnsnpBcIiwgXCLsg4Htkogg7Lm07YWM6rOg66asXCIpXG4tIHRhYmxlOiBzbmFrZV9jYXNl66GcIOuQnCDthYzsnbTruJTrqoUgKOyYiDogXCJ1c2Vyc1wiLCBcInByb2R1Y3RfY2F0ZWdvcmllc1wiKVxuLSBwYXJlbnRJZDog67aA66qoIEVudGl0eSBJRCAo7ISg7YOd7IKs7ZWtKVxuLSBwcm9wczogRW50aXR57J2YIO2UhOuhnO2NvO2LsCDrsLDsl7QgKOychCDrrLjshJzsnZggUHJvcGVydHkgVHlwZXMg7LC46rOgKVxuLSBpbmRleGVzOiDsnbjrjbHsiqQg67Cw7Je0XG4tIHN1YnNldHM6IOyEnOu4jOyFiyDsoJXsnZggKOq4sOuzuOqwkjogeyBBOiBbXCJpZFwiXSB9KVxuLSBlbnVtczogRW51bSDsoJXsnZhcblxuIyMjIEVudGl0eSDsiJjsoJUgKHVwZGF0ZUVudGl0eSlcbuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWgIOuVjCB1cGRhdGVFbnRpdHkg64+E6rWs66W8IOyCrOyaqe2VmOyEuOyalC4gRW51bSDstpTqsIAsIHByb3BzIOy2lOqwgC/siJjsoJUsIGluZGV4ZXMg7IiY7KCVIOuTsSDrqqjrk6Ag7IiY7KCVIOyekeyXheyXkCDsgqzsmqntlanri4jri6QuXG4tIGVudGl0eUlkOiDsiJjsoJXtlaAgRW50aXR5IElEXG4tIHVwZGF0ZXM6IOyImOygle2VoCDtlYTrk5zrk6QgKOu2gOu2hCDsl4XrjbDsnbTtirgpXG4gIC0gdGl0bGU6IOyXlO2LsO2LsCDtlZzquIAg7KCc66qpXG4gIC0gdGFibGU6IO2FjOydtOu4lOuqhVxuICAtIHByb3BzOiDstpTqsIDtlaAg7ZSE66Gc7Y287YuwIOuwsOyXtCAo6riw7KG0IHByb3Bz7JeQIOy2lOqwgCwg6rCZ7J2AIOydtOumhOydtOuptCDqtZDssrQpXG4gIC0gaW5kZXhlczog7LaU6rCA7ZWgIOyduOuNseyKpCDrsLDsl7QgKOq4sOyhtCBpbmRleGVz7JeQIOy2lOqwgClcbiAgLSBzdWJzZXRzOiDshJzruIzshYsg7KCV7J2YICjquLDsobQgc3Vic2V0c+yXkCDrs5HtlakpXG4gIC0gZW51bUxhYmVsczogRW51bSDsoJXsnZggKOq4sOyhtCBlbnVtTGFiZWxz7JeQIOuzke2VqSlcbi0gbW9kZTogXCJtZXJnZVwiKOq4sOuzuOqwkikg65iQ64qUIFwicmVwbGFjZVwiXG4gIC0gbWVyZ2U6IOq4sOyhtCDqsJLsl5Ag67OR7ZWpXG4gIC0gcmVwbGFjZTog7ZW064u5IO2VhOuTnCDsoITssrQg6rWQ7LK0XG5cbuyYiOyLnDogRW1wbG95ZWXsl5Ag7IOIIEVudW0g7LaU6rCAXG51cGRhdGVFbnRpdHkoeyBlbnRpdHlJZDogXCJFbXBsb3llZVwiLCB1cGRhdGVzOiB7IGVudW1MYWJlbHM6IHsgXCJFbXBsb3llZVJvbGVcIjogeyBcImFkbWluXCI6IFwi6rSA66as7J6QXCIsIFwidXNlclwiOiBcIuydvOuwmFwiIH0gfSB9IH0pXG5cbuyYiOyLnDogUHJvamVjdOyXkCDsg4gg7ZSE66Gc7Y287YuwIOy2lOqwgFxudXBkYXRlRW50aXR5KHsgZW50aXR5SWQ6IFwiUHJvamVjdFwiLCB1cGRhdGVzOiB7IHByb3BzOiBbeyBuYW1lOiBcInByaW9yaXR5XCIsIHR5cGU6IFwiaW50ZWdlclwiLCBkZXNjOiBcIuyasOyEoOyInOychFwiIH1dIH0gfSlcblxuIyMg7ZWE7IiYIOyCrO2VrVxuLSBFbnRpdHnsnZggcHJvcHPsl5DripQg7LWc7IaM7ZWcIGlkKGludGVnZXIsIHVuc2lnbmVkKSwgY3JlYXRlZF9hdCh0aW1lc3RhbXAp6rCAIO2PrO2VqOuQmOyWtOyVvCDtlanri4jri6QuXG4tIHJlbGF0aW9uIO2VhOuTnOuKlCBvblVwZGF0ZSwgb25EZWxldGXqsIAg7ZWE7IiY7J6F64uI64ukLiAo7JiI7Jm4OiBPbmVUb09uZeyXkOyEnCBoYXNKb2luQ29sdW1u7J20IGZhbHNl7J24IOqyveyasClcbi0gRW51bSBJROuKlCDrs7TthrUgRW50aXR5SWQgKyDsho3shLHrqoUg7ZiV7YOc7J6F64uI64ukICjsmIg6IFVzZXJTdGF0dXMsIFByb2R1Y3RUeXBlKVxuLSBzdWJzZXTsl5DshJwg64uk66W4IOyXlO2LsO2LsOydmCDtlITroZztjbzti7Drpbwg7LC47KGw7ZWgIOuVjOuKlCDrsJjrk5zsi5wg7ZW064u5IOyXlO2LsO2LsOydmCDsi6TsoJwg7ZSE66Gc7Y287Yuw66qF7J2EIOyCrOyaqe2VmOyEuOyalC5cblxuIyMg6rKA7KadIOyYpOulmCDsspjrpqxcbuuPhOq1rCDtmLjstpwg6rKw6rO866GcIOqygOymnSDsmKTrpZgodmFsaWRhdGlvbkVycm9ycynqsIAg67CY7ZmY65CY66m0OlxuMS4g7Jik66WYIOuplOyLnOyngOulvCDrtoTshJ3tlZjsl6wg66y47KCc7KCQ7J2EIO2MjOyVhe2VmOyEuOyalC5cbjIuIOyYpOulmOulvCDsiJjsoJXtlZwg642w7J207YSw66GcIGNyZWF0ZUVudGl0eeulvCDri6Tsi5wg7Zi47Lac7ZWY7IS47JqULlxuMy4g7IKs7Jqp7J6Q7JeQ6rKMIOyYpOulmOulvCDqt7jrjIDroZwg7KCE64us7ZWY7KeAIOunkOqzoCwg7IiY7KCVIO2bhCDsnqzsi5zrj4TtlZjshLjsmpQuXG5cbiMjIyDsnbzrsJjsoIHsnbgg6rKA7KadIOyYpOulmOyZgCDsiJjsoJUg67Cp67KVXG58IOyYpOulmCDrqZTsi5zsp4AgfCDsiJjsoJUg67Cp67KVIHxcbnwtLS0tLS0tLS0tLS18LS0tLS0tLS0tLXxcbnwgXCJpZCDtlITroZztjbzti7DqsIAg7ZWE7IiYXCIgfCBwcm9wc+yXkCB7IG5hbWU6IFwiaWRcIiwgdHlwZTogXCJpbnRlZ2VyXCIsIHVuc2lnbmVkOiB0cnVlIH0g7LaU6rCAIHxcbnwgXCJjcmVhdGVkX2F0IO2UhOuhnO2NvO2LsOqwgCDtlYTsiJhcIiB8IHByb3Bz7JeQIHsgbmFtZTogXCJjcmVhdGVkX2F0XCIsIHR5cGU6IFwidGltZXN0YW1wXCIsIGRiRGVmYXVsdDogXCJDVVJSRU5UX1RJTUVTVEFNUFwiIH0g7LaU6rCAIHxcbnwgXCJYeHhPcmRlckJ5IGVudW3snbQg7ZWE7IiYXCIgfCBlbnVtc+yXkCB7IFwiWHh4T3JkZXJCeVwiOiB7IFwiaWQtZGVzY1wiOiBcIklE7LWc7Iug7IicXCIgfSB9IOy2lOqwgCB8XG58IFwiWHh4U2VhcmNoRmllbGQgZW51beydtCDtlYTsiJhcIiB8IGVudW1z7JeQIHsgXCJYeHhTZWFyY2hGaWVsZFwiOiB7IFwiaWRcIjogXCJJRFwiIH0gfSDstpTqsIAgfFxufCBcInN0cmluZyDtg4DsnoXsnYAgbGVuZ3Ro6rCAIO2VhOyImFwiIHwg7ZW064u5IHByb3Dsl5AgbGVuZ3RoIOy2lOqwgCAo7JiIOiAyNTUpIHxcbnwgXCJ0ZXh0IO2DgOyeheydgCB0ZXh0VHlwZeydtCDtlYTsiJhcIiB8IO2VtOuLuSBwcm9w7JeQIHRleHRUeXBlIOy2lOqwgCAoXCJ0ZXh0XCIsIFwibWVkaXVtdGV4dFwiLCBcImxvbmd0ZXh0XCIpIHxcbnwgXCJvblVwZGF0ZeqwgCDtlYTsiJhcIiB8IO2VtOuLuSByZWxhdGlvbiBwcm9w7JeQIG9uVXBkYXRlLCBvbkRlbGV0ZSDstpTqsIAgKFwiQ0FTQ0FERVwiKSB8XG4gICAgICBgO1xuXG4gICAgcmV0dXJuIHN0cmVhbVRleHQoe1xuICAgICAgbW9kZWw6IHRoaXMubW9kZWwgYXMgdW5rbm93biBhcyBMYW5ndWFnZU1vZGVsLFxuICAgICAgc3lzdGVtOiBzeXN0ZW1NZXNzYWdlLFxuICAgICAgbWVzc2FnZXMsXG4gICAgICBzdG9wV2hlbjogc3RlcENvdW50SXMoMiksXG4gICAgICB0b29sczoge1xuICAgICAgICBjcmVhdGVFbnRpdHk6IHRvb2woe1xuICAgICAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAgICAgXCLsg4jroZzsmrQgRW50aXR566W8IOyDneyEse2VqeuLiOuLpC4g7IKs7Jqp7J6Q6rCAIOyDiOuhnOyatCDsl5Tti7Dti7Drgpgg7YWM7J2067iUIOyDneyEseydhCDsmpTssq3tlaAg65WMIOyCrOyaqe2VmOyEuOyalC5cIixcbiAgICAgICAgICBpbnB1dFNjaGVtYTogVGVtcGxhdGVPcHRpb25zLnNoYXBlLmVudGl0eSxcbiAgICAgICAgICBleGVjdXRlOiBhc3luYyAoXG4gICAgICAgICAgICBlbnRpdHksXG4gICAgICAgICAgKTogUHJvbWlzZTx7XG4gICAgICAgICAgICBzdWNjZXNzOiBib29sZWFuO1xuICAgICAgICAgICAgZW50aXR5SWQ6IHN0cmluZztcbiAgICAgICAgICAgIGVycm9yPzogc3RyaW5nO1xuICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycz86IFZhbGlkYXRpb25FcnJvcltdO1xuICAgICAgICAgIH0+ID0+IHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgIC8vIOyeheugpSDqsoDspp1cbiAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbihlbnRpdHkpO1xuXG4gICAgICAgICAgICAgIGlmICh2YWxpZGF0aW9uRXJyb3JzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgICAgICAgICAgICBlbnRpdHlJZDogZW50aXR5LmVudGl0eUlkLFxuICAgICAgICAgICAgICAgICAgZXJyb3I6IGDqsoDspp0g7Jik66WYOiAke3ZhbGlkYXRpb25FcnJvcnMubWFwKChlKSA9PiBgWyR7ZS5maWVsZH1dICR7ZS5tZXNzYWdlfWApLmpvaW4oXCIsIFwiKX1gLFxuICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbkVycm9ycyxcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgYXdhaXQgU29uYW11LnN5bmNlci5jcmVhdGVFbnRpdHkoe1xuICAgICAgICAgICAgICAgIHN1YnNldHM6IHsgQTogW1wiaWRcIl0gfSxcbiAgICAgICAgICAgICAgICBlbnVtczoge30sXG4gICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAvLyBFbnRpdHlNYW5hZ2VyIOumrOuhnOuTnFxuICAgICAgICAgICAgICBhd2FpdCBFbnRpdHlNYW5hZ2VyLnJlbG9hZCgpO1xuXG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQgfTtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBlIGluc3RhbmNlb2YgRXJyb3IgPyBlLm1lc3NhZ2UgOiBcIlVua25vd24gZXJyb3JcIjtcbiAgICAgICAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVudGl0eUlkOiBlbnRpdHkuZW50aXR5SWQsIGVycm9yIH07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgfSksXG4gICAgICAgIHVwZGF0ZUVudGl0eTogdG9vbCh7XG4gICAgICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICAgICBcIuq4sOyhtCBFbnRpdHnrpbwg7IiY7KCV7ZWp64uI64ukLiBFbnVtIOy2lOqwgCwgcHJvcHMg7LaU6rCAL+yImOyglSwgaW5kZXhlcyDsiJjsoJUsIHN1YnNldHMg7IiY7KCVIOuTsSDrqqjrk6Ag7JeU7Yuw7YuwIOyImOyglSDsnpHsl4Xsl5Ag7IKs7Jqp7ZWY7IS47JqULlwiLFxuICAgICAgICAgIGlucHV0U2NoZW1hOiB6Lm9iamVjdCh7XG4gICAgICAgICAgICBlbnRpdHlJZDogei5zdHJpbmcoKS5kZXNjcmliZShcIuyImOygle2VoCBFbnRpdHkgSURcIiksXG4gICAgICAgICAgICB1cGRhdGVzOiBUZW1wbGF0ZU9wdGlvbnMuc2hhcGUuZW50aXR5LnBhcnRpYWwoKS5kZXNjcmliZShcIuyImOygle2VoCDtlYTrk5zrk6RcIiksXG4gICAgICAgICAgICBtb2RlOiB6XG4gICAgICAgICAgICAgIC5lbnVtKFtcIm1lcmdlXCIsIFwicmVwbGFjZVwiXSlcbiAgICAgICAgICAgICAgLm9wdGlvbmFsKClcbiAgICAgICAgICAgICAgLmRlc2NyaWJlKFwi7IiY7KCVIOuqqOuTnDogbWVyZ2Uo6riw67O46rCSLCDquLDsobQg6rCS7JeQIOuzke2VqSkg65iQ64qUIHJlcGxhY2Uo7KCE7LK0IOq1kOyytClcIiksXG4gICAgICAgICAgfSksXG4gICAgICAgICAgZXhlY3V0ZTogYXN5bmMgKHtcbiAgICAgICAgICAgIGVudGl0eUlkLFxuICAgICAgICAgICAgdXBkYXRlcyxcbiAgICAgICAgICAgIG1vZGUgPSBcIm1lcmdlXCIsXG4gICAgICAgICAgfSk6IFByb21pc2U8e1xuICAgICAgICAgICAgc3VjY2VzczogYm9vbGVhbjtcbiAgICAgICAgICAgIGVudGl0eUlkOiBzdHJpbmc7XG4gICAgICAgICAgICBlcnJvcj86IHN0cmluZztcbiAgICAgICAgICAgIHZhbGlkYXRpb25FcnJvcnM/OiBWYWxpZGF0aW9uRXJyb3JbXTtcbiAgICAgICAgICB9PiA9PiB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICBjb25zdCBlbnRpdHkgPSBFbnRpdHlNYW5hZ2VyLmdldChlbnRpdHlJZCk7XG5cbiAgICAgICAgICAgICAgLy8gVXBkYXRlIGJhc2ljIHByb3BlcnRpZXNcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMuZW50aXR5SWQgIT09IHVuZGVmaW5lZCkgZW50aXR5LmlkID0gdXBkYXRlcy5lbnRpdHlJZDtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMucGFyZW50SWQgIT09IHVuZGVmaW5lZCkgZW50aXR5LnBhcmVudElkID0gdXBkYXRlcy5wYXJlbnRJZDtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMudGl0bGUgIT09IHVuZGVmaW5lZCkgZW50aXR5LnRpdGxlID0gdXBkYXRlcy50aXRsZTtcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMudGFibGUgIT09IHVuZGVmaW5lZCkgZW50aXR5LnRhYmxlID0gdXBkYXRlcy50YWJsZTtcblxuICAgICAgICAgICAgICAvLyBwcm9wczogbWVyZ2Ug7IucIOydtOumhCDquLDspIAg67OR7ZWpLCByZXBsYWNlIOyLnCDqtZDssrRcbiAgICAgICAgICAgICAgaWYgKHVwZGF0ZXMucHJvcHMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIGlmIChtb2RlID09PSBcInJlcGxhY2VcIikge1xuICAgICAgICAgICAgICAgICAgZW50aXR5LnByb3BzID0gdXBkYXRlcy5wcm9wcyBhcyBFbnRpdHlQcm9wW107XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIGZvciAoY29uc3QgbmV3UHJvcCBvZiB1cGRhdGVzLnByb3BzKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGV4aXN0aW5nSW5kZXggPSBlbnRpdHkucHJvcHMuZmluZEluZGV4KChwKSA9PiBwLm5hbWUgPT09IG5ld1Byb3AubmFtZSk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChleGlzdGluZ0luZGV4ID49IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICBlbnRpdHkucHJvcHNbZXhpc3RpbmdJbmRleF0gPSBuZXdQcm9wIGFzIEVudGl0eVByb3A7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgZW50aXR5LnByb3BzLnB1c2gobmV3UHJvcCBhcyBFbnRpdHlQcm9wKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIC8vIGluZGV4ZXM6IG1lcmdlIOyLnCDstpTqsIAsIHJlcGxhY2Ug7IucIOq1kOyytFxuICAgICAgICAgICAgICBpZiAodXBkYXRlcy5pbmRleGVzICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICBlbnRpdHkuaW5kZXhlcyA9XG4gICAgICAgICAgICAgICAgICBtb2RlID09PSBcInJlcGxhY2VcIiA/IHVwZGF0ZXMuaW5kZXhlcyA6IFsuLi5lbnRpdHkuaW5kZXhlcywgLi4udXBkYXRlcy5pbmRleGVzXTtcbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIC8vIHN1YnNldHMsIHN1YnNldHNJbnRlcm5hbDogYXNzaWdu7Jy866GcIOuzke2VqSDrmJDripQg6rWQ7LK0XG4gICAgICAgICAgICAgIGlmICh1cGRhdGVzLnN1YnNldHMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIC8vIE5vcm1hbGl6ZSBzdWJzZXQgZmllbGRzOiBzZXBhcmF0ZSBpbnRvIHN1YnNldHMgKG5vcm1hbCkgYW5kIHN1YnNldHNJbnRlcm5hbCAoaW50ZXJuYWwpXG4gICAgICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZFN1YnNldHM6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nW10gfSA9IHt9O1xuICAgICAgICAgICAgICAgIGNvbnN0IG5vcm1hbGl6ZWRTdWJzZXRzSW50ZXJuYWw6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nW10gfSA9IHt9O1xuXG4gICAgICAgICAgICAgICAgZm9yIChjb25zdCBba2V5LCBmaWVsZHNdIG9mIE9iamVjdC5lbnRyaWVzKHVwZGF0ZXMuc3Vic2V0cykpIHtcbiAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWRTdWJzZXRzW2tleV0gPSBmaWVsZHNcbiAgICAgICAgICAgICAgICAgICAgLmZpbHRlcigoZikgPT4gIWlzSW50ZXJuYWxTdWJzZXRGaWVsZChmKSlcbiAgICAgICAgICAgICAgICAgICAgLm1hcChub3JtYWxpemVTdWJzZXRGaWVsZCk7XG4gICAgICAgICAgICAgICAgICBub3JtYWxpemVkU3Vic2V0c0ludGVybmFsW2tleV0gPSBmaWVsZHNcbiAgICAgICAgICAgICAgICAgICAgLmZpbHRlcihpc0ludGVybmFsU3Vic2V0RmllbGQpXG4gICAgICAgICAgICAgICAgICAgIC5tYXAobm9ybWFsaXplU3Vic2V0RmllbGQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzID1cbiAgICAgICAgICAgICAgICAgIG1vZGUgPT09IFwicmVwbGFjZVwiXG4gICAgICAgICAgICAgICAgICAgID8gbm9ybWFsaXplZFN1YnNldHNcbiAgICAgICAgICAgICAgICAgICAgOiB7IC4uLmVudGl0eS5zdWJzZXRzLCAuLi5ub3JtYWxpemVkU3Vic2V0cyB9O1xuICAgICAgICAgICAgICAgIGVudGl0eS5zdWJzZXRzSW50ZXJuYWwgPVxuICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCJcbiAgICAgICAgICAgICAgICAgICAgPyBub3JtYWxpemVkU3Vic2V0c0ludGVybmFsXG4gICAgICAgICAgICAgICAgICAgIDogeyAuLi5lbnRpdHkuc3Vic2V0c0ludGVybmFsLCAuLi5ub3JtYWxpemVkU3Vic2V0c0ludGVybmFsIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICBpZiAodXBkYXRlcy5lbnVtcyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgZW50aXR5LmVudW1MYWJlbHMgPVxuICAgICAgICAgICAgICAgICAgbW9kZSA9PT0gXCJyZXBsYWNlXCIgPyB1cGRhdGVzLmVudW1zIDogeyAuLi5lbnRpdHkuZW51bUxhYmVscywgLi4udXBkYXRlcy5lbnVtcyB9O1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgLy8g7KCA7J6lIOyghCDqsoDspp1cbiAgICAgICAgICAgICAgY29uc3QgdmFsaWRhdGlvbkVycm9ycyA9IHZhbGlkYXRlRW50aXR5SnNvbih7XG4gICAgICAgICAgICAgICAgLi4uZW50aXR5LFxuICAgICAgICAgICAgICAgIGVudGl0eUlkOiBlbnRpdHkuaWQsXG4gICAgICAgICAgICAgICAgZW51bXM6IGVudGl0eS5lbnVtTGFiZWxzLFxuICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvbkVycm9ycy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgZW50aXR5SWQsXG4gICAgICAgICAgICAgICAgICBlcnJvcjogYOqygOymnSDsmKTrpZg6ICR7dmFsaWRhdGlvbkVycm9ycy5tYXAoKGUpID0+IGBbJHtlLmZpZWxkfV0gJHtlLm1lc3NhZ2V9YCkuam9pbihcIiwgXCIpfWAsXG4gICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uRXJyb3JzLFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICBhd2FpdCBlbnRpdHkuc2F2ZSgpO1xuXG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IHRydWUsIGVudGl0eUlkIH07XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGVycm9yID0gZSBpbnN0YW5jZW9mIEVycm9yID8gZS5tZXNzYWdlIDogXCJVbmtub3duIGVycm9yXCI7XG4gICAgICAgICAgICAgIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlbnRpdHlJZCwgZXJyb3IgfTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICB9KSxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH1cbn1cblxuLyoqXG4gKiBFbnRpdHkgSlNPTuydtCBlbnRpdHkuaW5zdHJ1Y3Rpb25zLm1k7J2YIOq3nOy5meydhCDrlLDrpbTripTsp4Ag6rKA7Kad7ZWp64uI64ukLlxuICovXG5mdW5jdGlvbiB2YWxpZGF0ZUVudGl0eUpzb24oaW5wdXQ6IFRlbXBsYXRlT3B0aW9uc1tcImVudGl0eVwiXSk6IFZhbGlkYXRpb25FcnJvcltdIHtcbiAgY29uc3QgZXJyb3JzOiBWYWxpZGF0aW9uRXJyb3JbXSA9IFtdO1xuICBjb25zdCB7IGVudGl0eUlkLCBwcm9wcywgZW51bXMgfSA9IGlucHV0O1xuXG4gIC8vIDEuIGlkLCBjcmVhdGVkX2F0IHByb3Ag7ZWE7IiYXG4gIGNvbnN0IGhhc0lkUHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiaWRcIik7XG4gIGlmICghaGFzSWRQcm9wKSB7XG4gICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImlkIO2UhOuhnO2NvO2LsOqwgCDtlYTsiJjsnoXri4jri6QuXCIgfSk7XG4gIH1cbiAgY29uc3QgaGFzQ3JlYXRlZEF0UHJvcCA9IHByb3BzPy5zb21lKChwKSA9PiBwLm5hbWUgPT09IFwiY3JlYXRlZF9hdFwiKTtcbiAgaWYgKCFoYXNDcmVhdGVkQXRQcm9wKSB7XG4gICAgZXJyb3JzLnB1c2goeyBmaWVsZDogXCJwcm9wc1wiLCBtZXNzYWdlOiBcImNyZWF0ZWRfYXQg7ZSE66Gc7Y287Yuw6rCAIO2VhOyImOyeheuLiOuLpC5cIiB9KTtcbiAgfVxuXG4gIC8vIDIuIO2VhOyImCBlbnVtIOqygOymnTogRW50aXR5TmFtZU9yZGVyQnksIEVudGl0eU5hbWVTZWFyY2hGaWVsZFxuICBjb25zdCBvcmRlckJ5RW51bUlkID0gYCR7ZW50aXR5SWR9T3JkZXJCeWA7XG4gIGNvbnN0IHNlYXJjaEZpZWxkRW51bUlkID0gYCR7ZW50aXR5SWR9U2VhcmNoRmllbGRgO1xuXG4gIGlmICghZW51bXM/LltvcmRlckJ5RW51bUlkXSkge1xuICAgIGVycm9ycy5wdXNoKHtcbiAgICAgIGZpZWxkOiBcImVudW1zXCIsXG4gICAgICBtZXNzYWdlOiBgJHtvcmRlckJ5RW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkLWRlc2NcIjogXCJJROy1nOyLoOyInFwiIH0pYCxcbiAgICB9KTtcbiAgfVxuICBpZiAoIWVudW1zPy5bc2VhcmNoRmllbGRFbnVtSWRdKSB7XG4gICAgZXJyb3JzLnB1c2goe1xuICAgICAgZmllbGQ6IFwiZW51bXNcIixcbiAgICAgIG1lc3NhZ2U6IGAke3NlYXJjaEZpZWxkRW51bUlkfSBlbnVt7J20IO2VhOyImOyeheuLiOuLpC4gKOyYiDogeyBcImlkXCI6IFwiSURcIiB9KWAsXG4gICAgfSk7XG4gIH1cblxuICAvLyAzLiBlbnVtIHByb3DsnZggaWTqsIAgZW51bXPsl5Ag7KCV7J2Y65CY7Ja0IOyeiOuKlOyngCDtmZXsnbggKGNyb3NzLWZpZWxkIOqygOymnSlcbiAgZm9yIChjb25zdCBwcm9wIG9mIHByb3BzID8/IFtdKSB7XG4gICAgaWYgKHByb3AudHlwZSA9PT0gXCJlbnVtXCIgJiYgIWVudW1zPy5bcHJvcC5pZF0pIHtcbiAgICAgIGVycm9ycy5wdXNoKHtcbiAgICAgICAgZmllbGQ6IGBwcm9wcy4ke3Byb3AubmFtZX1gLFxuICAgICAgICBtZXNzYWdlOiBgZW51bSBpZCBcIiR7cHJvcC5pZH1cIuqwgCBlbnVtc+yXkCDsoJXsnZjrkJjslrQg7J6I7KeAIOyViuyKteuLiOuLpC5gLFxuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIGVycm9ycztcbn1cblxuZXhwb3J0IGNvbnN0IGFpQ2xpZW50ID0gbmV3IEFJQ2xpZW50KCk7XG4iXSwibmFtZXMiOlsiYW50aHJvcGljIiwic3RlcENvdW50SXMiLCJzdHJlYW1UZXh0IiwidG9vbCIsImFzc2VydCIsImZzIiwicGF0aCIsInoiLCJTb25hbXUiLCJFbnRpdHlNYW5hZ2VyIiwiaXNJbnRlcm5hbFN1YnNldEZpZWxkIiwibm9ybWFsaXplU3Vic2V0RmllbGQiLCJUZW1wbGF0ZU9wdGlvbnMiLCJub25OdWxsYWJsZSIsIkFJQ2xpZW50IiwibW9kZWwiLCJpbml0IiwiY29uc29sZSIsImxvZyIsImhhbmRsZUZpeHR1cmUiLCJtZXNzYWdlcyIsImZpeHR1cmVSZWNvcmRzIiwidXNlZEVudGl0eUlkcyIsIlNldCIsIm1hcCIsInIiLCJlbnRpdHlJZCIsImVudGl0eVN0cnVjdHVyZXMiLCJlbnRpdHkiLCJnZXQiLCJpZCIsInRhYmxlIiwicHJvcHMiLCJyZWxhdGlvbnMiLCJlbnVtTGFiZWxzIiwic3lzdGVtTWVzc2FnZSIsIkpTT04iLCJzdHJpbmdpZnkiLCJzeXN0ZW0iLCJ0b29scyIsInVwZGF0ZUZpeHR1cmVzIiwiZGVzY3JpcHRpb24iLCJpbnB1dFNjaGVtYSIsIm9iamVjdCIsInVwZGF0ZXMiLCJhcnJheSIsImZpeHR1cmVJZCIsInN0cmluZyIsImRlc2NyaWJlIiwicmVjb3JkIiwidW5rbm93biIsImV4ZWN1dGUiLCJ1cGRhdGVkUmVjb3JkcyIsInVwZGF0ZSIsImZpbmQiLCJ1IiwiY29sdW1uTmFtZSIsIm5ld1ZhbHVlIiwiT2JqZWN0IiwiZW50cmllcyIsImNvbHVtbnMiLCJ2YWx1ZSIsInN1Y2Nlc3MiLCJjcmVhdGVGaXh0dXJlcyIsImZpeHR1cmVzIiwibnVtYmVyIiwibmV3UmVjb3JkcyIsImZpeHR1cmUiLCJwcm9wIiwidHlwZSIsIm5hbWUiLCJEYXRlIiwidG9JU09TdHJpbmciLCJyZWxhdGlvblR5cGUiLCJBcnJheSIsImlzQXJyYXkiLCJmaWx0ZXIiLCJmZXRjaGVkUmVjb3JkcyIsImJlbG9uZ3NSZWNvcmRzIiwib3ZlcnJpZGUiLCJuZXdSZWNvcmQiLCJfY29sTmFtZSIsImNvbCIsInJlbGF0ZWRFbnRpdHlJZCIsIndpdGgiLCJyZWxhdGVkSWRzIiwicmVsYXRlZElkIiwicmVsYXRlZEZpeHR1cmVJZCIsInJlbGF0ZWRSZWNvcmQiLCJyZXZlcnNlQ29sIiwiYyIsInJldmVyc2VDb2xOYW1lIiwicmV2ZXJzZUNvbFZhbHVlIiwiY3VycmVudFZhbHVlIiwiaW5jbHVkZXMiLCJoYW5kbGVFbnRpdHkiLCJpbnN0cnVjdGlvbnNQYXRoIiwiam9pbiIsImRpcm5hbWUiLCJpbnN0cnVjdGlvbnMiLCJyZWFkRmlsZVN5bmMiLCJlbnRpdHlJZHMiLCJnZXRBbGxJZHMiLCJleGlzdGluZ0VudGl0aWVzIiwidGl0bGUiLCJwIiwiZGVzYyIsInN0b3BXaGVuIiwiY3JlYXRlRW50aXR5Iiwic2hhcGUiLCJ2YWxpZGF0aW9uRXJyb3JzIiwidmFsaWRhdGVFbnRpdHlKc29uIiwibGVuZ3RoIiwiZXJyb3IiLCJlIiwiZmllbGQiLCJtZXNzYWdlIiwic3luY2VyIiwic3Vic2V0cyIsIkEiLCJlbnVtcyIsInJlbG9hZCIsIkVycm9yIiwidXBkYXRlRW50aXR5IiwicGFydGlhbCIsIm1vZGUiLCJlbnVtIiwib3B0aW9uYWwiLCJ1bmRlZmluZWQiLCJwYXJlbnRJZCIsIm5ld1Byb3AiLCJleGlzdGluZ0luZGV4IiwiZmluZEluZGV4IiwicHVzaCIsImluZGV4ZXMiLCJub3JtYWxpemVkU3Vic2V0cyIsIm5vcm1hbGl6ZWRTdWJzZXRzSW50ZXJuYWwiLCJrZXkiLCJmaWVsZHMiLCJmIiwic3Vic2V0c0ludGVybmFsIiwic2F2ZSIsImlucHV0IiwiZXJyb3JzIiwiaGFzSWRQcm9wIiwic29tZSIsImhhc0NyZWF0ZWRBdFByb3AiLCJvcmRlckJ5RW51bUlkIiwic2VhcmNoRmllbGRFbnVtSWQiLCJhaUNsaWVudCJdLCJtYXBwaW5ncyI6IkFBQUEsaUZBQWlGLEdBQ2pGLFNBQVNBLFNBQVMsUUFBUSxvQkFBb0I7QUFDOUMsU0FJRUMsV0FBVyxFQUNYQyxVQUFVLEVBQ1ZDLElBQUksUUFDQyxLQUFLO0FBQ1osT0FBT0MsWUFBWSxTQUFTO0FBQzVCLE9BQU9DLFFBQVEsS0FBSztBQUNwQixPQUFPQyxVQUFVLE9BQU87QUFDeEIsU0FBU0MsQ0FBQyxRQUFRLE1BQU07QUFDeEIsU0FBU0MsTUFBTSxRQUFRLGtCQUFTO0FBQ2hDLFNBQVNDLGFBQWEsUUFBUSw4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