@sayren/store-sdk 0.30.13 → 0.30.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +972 -21
- package/dist/index.mjs +368 -4
- package/package.json +1 -1
- package/src/client.ts +58 -0
- package/src/index.ts +1 -0
- package/src/schemas/ai.ts +376 -0
- package/src/schemas/app-manifest.ts +1 -1
- package/src/schemas/apps.ts +1 -1
- package/src/schemas/upload.ts +12 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 상점 플랫폼 AI 채팅(이슈 #124) — 대화·메시지·실행과 실행 이벤트 스트림의 계약.
|
|
5
|
+
*
|
|
6
|
+
* 상점 플랫폼 전용이다(`@ConsoleOnly`, 공개 OpenAPI 문서 제외). 메시지를 보내면 api가 실행(run)을 만들어 202로 `streamId`를 돌려주고,
|
|
7
|
+
* 응답은 별도 중계(`streamUrl`, SSE)로 받는다. 실행은 브라우저와 무관하게 서버에서 끝까지 돈다 — 다시 열면 진행 중 실행의 스트림에
|
|
8
|
+
* 처음부터 다시 붙거나(보관 1시간) 확정된 메시지를 읽는다.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 첨부 하나 — 업로드(`POST /v1/uploads`, 목적 `ai-attachment`)가 돌려준 공개 주소 */
|
|
12
|
+
export const aiAttachmentSchema = z.object({
|
|
13
|
+
url: z.string().url().max(2048),
|
|
14
|
+
name: z.string().trim().min(1).max(200),
|
|
15
|
+
mediaType: z.string().trim().min(1).max(100).describe("image/* 또는 application/pdf"),
|
|
16
|
+
});
|
|
17
|
+
export type AiAttachment = z.infer<typeof aiAttachmentSchema>;
|
|
18
|
+
|
|
19
|
+
export const AI_MESSAGE_MAX_CHARS = 8_000;
|
|
20
|
+
export const AI_MESSAGE_MAX_ATTACHMENTS = 5;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 편집 미리보기에서 고른 요소(#65) — 미리보기 안 선택 스크립트가 만든 값이라 사이트 코드가 꾸밀 수 있다. api는 모델에 넘길 때 데이터로
|
|
24
|
+
* 감싼다. `source`는 그 컴포넌트를 **사용한** 위치다(작업 트리 상대 경로). React 개발 정보가 없으면 null이다
|
|
25
|
+
*/
|
|
26
|
+
export const aiPreviewSelectionSchema = z.object({
|
|
27
|
+
component: z.string().max(120).nullable(),
|
|
28
|
+
source: z
|
|
29
|
+
.object({
|
|
30
|
+
path: z.string().max(300),
|
|
31
|
+
line: z.number().int().positive().max(1_000_000),
|
|
32
|
+
column: z.number().int().positive().max(100_000).optional(),
|
|
33
|
+
})
|
|
34
|
+
.nullable(),
|
|
35
|
+
tag: z.string().max(40),
|
|
36
|
+
text: z.string().max(200),
|
|
37
|
+
pagePath: z.string().max(500),
|
|
38
|
+
});
|
|
39
|
+
export type AiPreviewSelection = z.infer<typeof aiPreviewSelectionSchema>;
|
|
40
|
+
|
|
41
|
+
/** 실행 상태 — 끝난 상태는 `DONE`·`FAILED`·`CANCELLED`다 */
|
|
42
|
+
export const aiRunStatusSchema = z.enum([
|
|
43
|
+
"QUEUED",
|
|
44
|
+
"RUNNING",
|
|
45
|
+
"CANCEL_REQUESTED",
|
|
46
|
+
"DONE",
|
|
47
|
+
"FAILED",
|
|
48
|
+
"CANCELLED",
|
|
49
|
+
]);
|
|
50
|
+
export type AiRunStatus = z.infer<typeof aiRunStatusSchema>;
|
|
51
|
+
|
|
52
|
+
export const AI_RUN_ACTIVE_STATUSES: readonly AiRunStatus[] = [
|
|
53
|
+
"QUEUED",
|
|
54
|
+
"RUNNING",
|
|
55
|
+
"CANCEL_REQUESTED",
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/** 진행 중 실행의 스트림 접속 정보. `streamToken`은 이 스트림 하나만 읽는 단기 자격(Bearer)이다 */
|
|
59
|
+
export const aiStreamAccessSchema = z.object({
|
|
60
|
+
streamId: z.string(),
|
|
61
|
+
streamUrl: z.string().url(),
|
|
62
|
+
streamToken: z.string(),
|
|
63
|
+
});
|
|
64
|
+
export type AiStreamAccess = z.infer<typeof aiStreamAccessSchema>;
|
|
65
|
+
|
|
66
|
+
export const aiRunSchema = z.object({
|
|
67
|
+
id: z.string().describe("실행 id = streamId"),
|
|
68
|
+
status: aiRunStatusSchema,
|
|
69
|
+
errorCode: z.string().nullable(),
|
|
70
|
+
createdAt: z.string(),
|
|
71
|
+
finishedAt: z.string().nullable(),
|
|
72
|
+
});
|
|
73
|
+
export type AiRun = z.infer<typeof aiRunSchema>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 화면에 그리는 메시지 조각. 모델 원문(tool_use 블록 등)은 api가 보관하고 화면에는 이 형태만 준다.
|
|
77
|
+
* - `text`: 글
|
|
78
|
+
* - `tool`: 도구 호출 한 번(이름은 사람이 읽는 라벨, 결과 요약·링크)
|
|
79
|
+
* - `choices`: 선택지 버튼(가장 최근 것만 누를 수 있다)
|
|
80
|
+
* - `attachment`: 사용자가 올린 첨부
|
|
81
|
+
* - `approval`: 승인 대기로 남은 쓰기
|
|
82
|
+
*/
|
|
83
|
+
export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
84
|
+
z.object({ type: z.literal("text"), text: z.string() }),
|
|
85
|
+
z.object({
|
|
86
|
+
type: z.literal("tool"),
|
|
87
|
+
toolCallId: z.string(),
|
|
88
|
+
name: z.string(),
|
|
89
|
+
label: z.string(),
|
|
90
|
+
status: z.enum(["ok", "error", "unknown"]),
|
|
91
|
+
summary: z.string().nullable(),
|
|
92
|
+
}),
|
|
93
|
+
z.object({
|
|
94
|
+
type: z.literal("choices"),
|
|
95
|
+
question: z.string(),
|
|
96
|
+
options: z
|
|
97
|
+
.array(z.object({ label: z.string(), value: z.string() }))
|
|
98
|
+
.min(1)
|
|
99
|
+
.max(6),
|
|
100
|
+
}),
|
|
101
|
+
z.object({ type: z.literal("attachment"), attachment: aiAttachmentSchema }),
|
|
102
|
+
/** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다(셀러가 승인함에서 결정한다) */
|
|
103
|
+
z.object({ type: z.literal("approval"), approvalId: z.string(), title: z.string() }),
|
|
104
|
+
/** 사용자가 미리보기에서 고른 요소(#65) */
|
|
105
|
+
z.object({ type: z.literal("selection"), selection: aiPreviewSelectionSchema }),
|
|
106
|
+
]);
|
|
107
|
+
export type AiMessagePart = z.infer<typeof aiMessagePartSchema>;
|
|
108
|
+
|
|
109
|
+
export const aiMessageSchema = z.object({
|
|
110
|
+
id: z.string(),
|
|
111
|
+
role: z.enum(["user", "assistant"]),
|
|
112
|
+
parts: z.array(aiMessagePartSchema),
|
|
113
|
+
runId: z.string().nullable(),
|
|
114
|
+
createdAt: z.string(),
|
|
115
|
+
});
|
|
116
|
+
export type AiMessage = z.infer<typeof aiMessageSchema>;
|
|
117
|
+
|
|
118
|
+
export const aiConversationSummarySchema = z.object({
|
|
119
|
+
id: z.string(),
|
|
120
|
+
title: z.string().nullable(),
|
|
121
|
+
createdAt: z.string(),
|
|
122
|
+
updatedAt: z.string(),
|
|
123
|
+
});
|
|
124
|
+
export type AiConversationSummary = z.infer<typeof aiConversationSummarySchema>;
|
|
125
|
+
|
|
126
|
+
export const aiConversationSchema = aiConversationSummarySchema.extend({
|
|
127
|
+
messages: z.array(aiMessageSchema),
|
|
128
|
+
/** 진행 중 실행(없으면 null) — 스트림에 다시 붙을 때 쓴다 */
|
|
129
|
+
activeRun: aiRunSchema.extend({ stream: aiStreamAccessSchema }).nullable(),
|
|
130
|
+
/** 마지막 실행(진행 중이 아니면 결과 표시용) */
|
|
131
|
+
lastRun: aiRunSchema.nullable(),
|
|
132
|
+
});
|
|
133
|
+
export type AiConversation = z.infer<typeof aiConversationSchema>;
|
|
134
|
+
|
|
135
|
+
export const createAiConversationRequestSchema = z.object({
|
|
136
|
+
/** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
|
|
137
|
+
draftId: z.string().max(64).optional(),
|
|
138
|
+
});
|
|
139
|
+
export type CreateAiConversationRequest = z.infer<typeof createAiConversationRequestSchema>;
|
|
140
|
+
|
|
141
|
+
export const createAiConversationResponseSchema = z.object({
|
|
142
|
+
conversation: aiConversationSummarySchema,
|
|
143
|
+
/** `draftId`로 시작했으면 첫 실행 */
|
|
144
|
+
run: aiStreamAccessSchema.nullable(),
|
|
145
|
+
});
|
|
146
|
+
export type CreateAiConversationResponse = z.infer<typeof createAiConversationResponseSchema>;
|
|
147
|
+
|
|
148
|
+
export const sendAiMessageRequestSchema = z
|
|
149
|
+
.object({
|
|
150
|
+
text: z.string().trim().max(AI_MESSAGE_MAX_CHARS),
|
|
151
|
+
attachments: z.array(aiAttachmentSchema).max(AI_MESSAGE_MAX_ATTACHMENTS).default([]),
|
|
152
|
+
/** 선택지 버튼으로 보낸 답이면 그 값(화면 표시는 `text`) */
|
|
153
|
+
choice: z.string().max(200).optional(),
|
|
154
|
+
/** 편집 미리보기에서 고른 요소(#65) — 모델은 이 요소를 기준으로 고친다 */
|
|
155
|
+
selection: aiPreviewSelectionSchema.optional(),
|
|
156
|
+
})
|
|
157
|
+
.refine((body) => body.text.length > 0 || body.attachments.length > 0, {
|
|
158
|
+
message: "내용이나 첨부가 있어야 합니다",
|
|
159
|
+
});
|
|
160
|
+
export type SendAiMessageRequest = z.input<typeof sendAiMessageRequestSchema>;
|
|
161
|
+
|
|
162
|
+
export const sendAiMessageResponseSchema = aiStreamAccessSchema.extend({
|
|
163
|
+
messageId: z.string(),
|
|
164
|
+
});
|
|
165
|
+
export type SendAiMessageResponse = z.infer<typeof sendAiMessageResponseSchema>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* AI가 바꾸는 화면 영역 — 쓰기 도구가 시작되면 이벤트에 싣고, 패널이 그 화면으로 옮겨 반영된 데이터를 다시 읽는다(셀러가 AI가 하는 일을 같이 본다).
|
|
169
|
+
* 경로는 상점 플랫폼이 정한다(영역 → 라우트). `id`가 있으면 상세 화면이다
|
|
170
|
+
*/
|
|
171
|
+
export const aiFocusAreaSchema = z.enum([
|
|
172
|
+
"products",
|
|
173
|
+
"categories",
|
|
174
|
+
"collections",
|
|
175
|
+
"option_sets",
|
|
176
|
+
"coupons",
|
|
177
|
+
"points",
|
|
178
|
+
"orders",
|
|
179
|
+
"claims",
|
|
180
|
+
"customers",
|
|
181
|
+
"inquiries",
|
|
182
|
+
"customer_inquiries",
|
|
183
|
+
"store_settings",
|
|
184
|
+
"shipping",
|
|
185
|
+
"addresses",
|
|
186
|
+
"payments",
|
|
187
|
+
"login",
|
|
188
|
+
"members",
|
|
189
|
+
"order_flows",
|
|
190
|
+
"webhooks",
|
|
191
|
+
"storefront",
|
|
192
|
+
]);
|
|
193
|
+
export type AiFocusArea = z.infer<typeof aiFocusAreaSchema>;
|
|
194
|
+
|
|
195
|
+
export const aiFocusSchema = z.object({
|
|
196
|
+
area: aiFocusAreaSchema,
|
|
197
|
+
id: z.string().optional(),
|
|
198
|
+
});
|
|
199
|
+
export type AiFocus = z.infer<typeof aiFocusSchema>;
|
|
200
|
+
|
|
201
|
+
// ─── 작업 트리 변경·되돌리기(#65) ─────────────────────────────────────────
|
|
202
|
+
// AI 실행이 스토어프론트 작업 트리를 바꾸면 경로마다 이전·이후를 남긴다. 셀러는 실행 단위로 되돌린다(전부 아니면 전무).
|
|
203
|
+
|
|
204
|
+
/** 변경 기록 보관 기간 — 지나면 내용을 비우고 되돌릴 수 없다(410 `AI_RUN_CHANGES_EXPIRED`) */
|
|
205
|
+
export const AI_RUN_FILE_CHANGES_RETENTION_DAYS = 7;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 실행 하나의 변경 상태
|
|
209
|
+
* - `APPLIED`: 되돌릴 수 있다(진행 중 실행이 없을 때)
|
|
210
|
+
* - `INCOMPLETE`: 결과를 모르는 쓰기·이전 내용이 너무 큰 경로가 있다(되돌릴 수 없다)
|
|
211
|
+
* - `REVERTING`·`REVERTED`·`REVERT_UNKNOWN`(되돌리기 결과를 모름 — 다시 하지 않는다)·`EXPIRED`
|
|
212
|
+
*/
|
|
213
|
+
export const aiRunChangesStatusSchema = z.enum([
|
|
214
|
+
"APPLIED",
|
|
215
|
+
"INCOMPLETE",
|
|
216
|
+
"REVERTING",
|
|
217
|
+
"REVERTED",
|
|
218
|
+
"REVERT_UNKNOWN",
|
|
219
|
+
"EXPIRED",
|
|
220
|
+
]);
|
|
221
|
+
export type AiRunChangesStatus = z.infer<typeof aiRunChangesStatusSchema>;
|
|
222
|
+
|
|
223
|
+
export const aiFileChangeKindSchema = z.enum(["created", "modified", "deleted"]);
|
|
224
|
+
export type AiFileChangeKind = z.infer<typeof aiFileChangeKindSchema>;
|
|
225
|
+
|
|
226
|
+
export const aiRunChangesSchema = z.object({
|
|
227
|
+
runId: z.string(),
|
|
228
|
+
status: aiRunChangesStatusSchema,
|
|
229
|
+
files: z.array(
|
|
230
|
+
z.object({
|
|
231
|
+
path: z.string(),
|
|
232
|
+
change: aiFileChangeKindSchema.describe("결과를 모르는 경로는 `modified`로 보인다"),
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
});
|
|
236
|
+
export type AiRunChanges = z.infer<typeof aiRunChangesSchema>;
|
|
237
|
+
|
|
238
|
+
/** `GET /v1/ai/conversations/{id}/file-changes` — 대화의 실행별 변경(실행 만든 순) */
|
|
239
|
+
export const aiConversationFileChangesSchema = z.object({
|
|
240
|
+
runs: z.array(aiRunChangesSchema),
|
|
241
|
+
});
|
|
242
|
+
export type AiConversationFileChanges = z.infer<typeof aiConversationFileChangesSchema>;
|
|
243
|
+
|
|
244
|
+
/** `GET /v1/ai/runs/{runId}/file-changes` — 변경 보기. 텍스트가 아닌 파일·보관이 끝난 내용은 null이다(diff는 화면이 계산한다) */
|
|
245
|
+
export const aiRunFileDiffSchema = aiRunChangesSchema.extend({
|
|
246
|
+
files: z.array(
|
|
247
|
+
z.object({
|
|
248
|
+
path: z.string(),
|
|
249
|
+
change: aiFileChangeKindSchema,
|
|
250
|
+
before: z.string().nullable(),
|
|
251
|
+
after: z.string().nullable(),
|
|
252
|
+
binary: z.boolean(),
|
|
253
|
+
}),
|
|
254
|
+
),
|
|
255
|
+
});
|
|
256
|
+
export type AiRunFileDiff = z.infer<typeof aiRunFileDiffSchema>;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* `POST /v1/ai/runs/{runId}/revert` — 실행이 바꾼 경로를 그 전 내용으로 한 번에 돌린다. 오류: 409 `AI_RUN_IN_PROGRESS`(대화에 진행 중 실행),
|
|
260
|
+
* 409 `AI_RUN_REVERT_CONFLICT`(`details.conflicts` — 그 뒤 바뀐 경로·결과를 모르는 쓰기), 409 `AI_RUN_ALREADY_REVERTED`,
|
|
261
|
+
* 410 `AI_RUN_CHANGES_EXPIRED`, 404 `AI_RUN_CHANGES_NOT_FOUND`, 502 `AI_RUN_REVERT_UNKNOWN`(결과를 모름 — 다시 하지 않는다)
|
|
262
|
+
*/
|
|
263
|
+
export const aiRunRevertResultSchema = z.object({
|
|
264
|
+
runId: z.string(),
|
|
265
|
+
status: z.literal("REVERTED"),
|
|
266
|
+
paths: z.array(z.string()),
|
|
267
|
+
});
|
|
268
|
+
export type AiRunRevertResult = z.infer<typeof aiRunRevertResultSchema>;
|
|
269
|
+
|
|
270
|
+
/** 409 `AI_RUN_REVERT_CONFLICT`의 `error.details` */
|
|
271
|
+
export const aiRunRevertConflictDetailsSchema = z.object({
|
|
272
|
+
conflicts: z.array(z.object({ path: z.string(), reason: z.enum(["CHANGED", "UNKNOWN"]) })),
|
|
273
|
+
});
|
|
274
|
+
export type AiRunRevertConflictDetails = z.infer<typeof aiRunRevertConflictDetailsSchema>;
|
|
275
|
+
|
|
276
|
+
// ─── 실행 이벤트(SSE) ────────────────────────────────────────────────────
|
|
277
|
+
// SSE 한 건 = `id: {seq}` · `event: {type}` · `data: {JSON}`. seq는 실행 안에서 1부터 늘고 끊긴 실행을 이어도 이어진다.
|
|
278
|
+
|
|
279
|
+
export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
280
|
+
/** 글 조각(약 100ms 묶음). 같은 `messageId`의 `text`에 이어 붙인다 */
|
|
281
|
+
z.object({ type: z.literal("delta"), messageId: z.string(), text: z.string() }),
|
|
282
|
+
z.object({
|
|
283
|
+
type: z.literal("tool_start"),
|
|
284
|
+
messageId: z.string(),
|
|
285
|
+
toolCallId: z.string(),
|
|
286
|
+
name: z.string(),
|
|
287
|
+
label: z.string(),
|
|
288
|
+
/** 쓰기 도구가 바꾸는 화면(읽기 도구는 없다) */
|
|
289
|
+
focus: aiFocusSchema.optional(),
|
|
290
|
+
}),
|
|
291
|
+
z.object({
|
|
292
|
+
type: z.literal("tool_result"),
|
|
293
|
+
messageId: z.string(),
|
|
294
|
+
toolCallId: z.string(),
|
|
295
|
+
status: z.enum(["ok", "error", "unknown"]),
|
|
296
|
+
summary: z.string().nullable(),
|
|
297
|
+
}),
|
|
298
|
+
z.object({
|
|
299
|
+
type: z.literal("choices"),
|
|
300
|
+
messageId: z.string(),
|
|
301
|
+
question: z.string(),
|
|
302
|
+
options: z.array(z.object({ label: z.string(), value: z.string() })),
|
|
303
|
+
}),
|
|
304
|
+
/** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다 */
|
|
305
|
+
z.object({
|
|
306
|
+
type: z.literal("approval"),
|
|
307
|
+
messageId: z.string(),
|
|
308
|
+
approvalId: z.string(),
|
|
309
|
+
title: z.string(),
|
|
310
|
+
}),
|
|
311
|
+
/** 메시지 하나가 확정됐다(이 시점의 내용은 대화 조회와 같다) */
|
|
312
|
+
z.object({ type: z.literal("message"), message: aiMessageSchema }),
|
|
313
|
+
z.object({ type: z.literal("error"), code: z.string(), message: z.string() }),
|
|
314
|
+
/** 실행이 끝났다. 뒤에 이벤트가 없다 */
|
|
315
|
+
z.object({ type: z.literal("done"), status: aiRunStatusSchema }),
|
|
316
|
+
]);
|
|
317
|
+
export type AiStreamEvent = z.infer<typeof aiStreamEventSchema>;
|
|
318
|
+
|
|
319
|
+
/** 중계에 쌓이는 이벤트 한 건 — 실행기 → 중계 `POST /internal/streams/{id}/events` */
|
|
320
|
+
export const aiStreamRecordSchema = z.object({
|
|
321
|
+
seq: z.number().int().positive(),
|
|
322
|
+
event: aiStreamEventSchema,
|
|
323
|
+
});
|
|
324
|
+
export type AiStreamRecord = z.infer<typeof aiStreamRecordSchema>;
|
|
325
|
+
|
|
326
|
+
// ─── 웹사이트 초안(#124 M2) ──────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
export const STORE_DRAFT_MAX_ATTACHMENTS = 5;
|
|
329
|
+
/** 초안 첨부 하나의 최대 크기(바이트) */
|
|
330
|
+
export const STORE_DRAFT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
331
|
+
/** 연결하지 않은 초안을 지우기까지 */
|
|
332
|
+
export const STORE_DRAFT_RETENTION_DAYS = 7;
|
|
333
|
+
|
|
334
|
+
export const createStoreDraftRequestSchema = z.object({
|
|
335
|
+
text: z.string().trim().min(1).max(AI_MESSAGE_MAX_CHARS),
|
|
336
|
+
attachments: z
|
|
337
|
+
.array(
|
|
338
|
+
z.object({
|
|
339
|
+
filename: z.string().trim().min(1).max(200),
|
|
340
|
+
size: z.number().int().positive().max(STORE_DRAFT_ATTACHMENT_MAX_BYTES),
|
|
341
|
+
}),
|
|
342
|
+
)
|
|
343
|
+
.max(STORE_DRAFT_MAX_ATTACHMENTS)
|
|
344
|
+
.default([]),
|
|
345
|
+
});
|
|
346
|
+
export type CreateStoreDraftRequest = z.input<typeof createStoreDraftRequestSchema>;
|
|
347
|
+
|
|
348
|
+
export const createStoreDraftResponseSchema = z.object({
|
|
349
|
+
draftId: z.string(),
|
|
350
|
+
/** 첨부마다 브라우저가 파일을 그대로 PUT할 자리(요청 순서와 같다) */
|
|
351
|
+
uploads: z.array(z.object({ uploadUrl: z.string().url(), url: z.string().url() })),
|
|
352
|
+
/** 다음 화면 — 상점 플랫폼 `/start?draft=…` */
|
|
353
|
+
continueUrl: z.string().url(),
|
|
354
|
+
});
|
|
355
|
+
export type CreateStoreDraftResponse = z.infer<typeof createStoreDraftResponseSchema>;
|
|
356
|
+
|
|
357
|
+
export const storeDraftSchema = z.object({
|
|
358
|
+
id: z.string(),
|
|
359
|
+
text: z.string(),
|
|
360
|
+
attachments: z.array(aiAttachmentSchema),
|
|
361
|
+
createdAt: z.string(),
|
|
362
|
+
/** 이 초안으로 시작한 상점(없으면 null) */
|
|
363
|
+
storeId: z.string().nullable(),
|
|
364
|
+
});
|
|
365
|
+
export type StoreDraft = z.infer<typeof storeDraftSchema>;
|
|
366
|
+
|
|
367
|
+
/** 로그인 전 초안 미리보기(`GET /v1/public/store-drafts/{id}`) — 로그인 화면이 「저장한 설명」을 보인다. 첨부는 이름만 */
|
|
368
|
+
export const storeDraftPreviewSchema = z.object({
|
|
369
|
+
id: z.string(),
|
|
370
|
+
text: z.string(),
|
|
371
|
+
attachments: z.array(z.object({ name: z.string(), mediaType: z.string() })),
|
|
372
|
+
expiresAt: z.string(),
|
|
373
|
+
/** 이미 상점에서 쓴 초안이면 true(다시 시작할 수 없다) */
|
|
374
|
+
used: z.boolean(),
|
|
375
|
+
});
|
|
376
|
+
export type StoreDraftPreview = z.infer<typeof storeDraftPreviewSchema>;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./webhook-event";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* 서드파티 앱 매니페스트 `
|
|
11
|
+
* 서드파티 앱 매니페스트 `manifest.json` (이슈 #22) — CLI(`sayren app`)·api(빌드 `VERIFY`·버전 판정·동의 화면 자료)·런타임이
|
|
12
12
|
* 같은 원천을 쓴다. 매니페스트는 정적 JSON이고 빌드 없이 읽는다.
|
|
13
13
|
*
|
|
14
14
|
* 기능 하나는 **capability** 하나다(`APP_CAPABILITIES`). capability는 매니페스트 조각·동의 문단·넓어짐 판정(재동의 조건)을 한 곳에 둔다.
|
package/src/schemas/apps.ts
CHANGED
|
@@ -445,7 +445,7 @@ export type DeveloperAppCreate = z.infer<typeof developerAppCreateSchema>;
|
|
|
445
445
|
|
|
446
446
|
/** `PUT /platform/v1/developer/apps/{appId}/versions` — CLI `app deploy`가 올리는 번들 */
|
|
447
447
|
export const appArtifactUploadSchema = z.object({
|
|
448
|
-
manifest: z.unknown().describe("
|
|
448
|
+
manifest: z.unknown().describe("manifest.json 원문(JSON)"),
|
|
449
449
|
packageJson: z.string().max(256 * 1024),
|
|
450
450
|
lockfile: z
|
|
451
451
|
.string()
|
package/src/schemas/upload.ts
CHANGED
|
@@ -6,8 +6,14 @@ import { z } from "zod";
|
|
|
6
6
|
* - `product-image`: 상품 목록·상세의 상품 이미지. 목록 썸네일로 쓰이므로 정지 이미지만 받는다.
|
|
7
7
|
* - `description`: 상세설명 본문에 삽입하는 미디어. 동영상과 svg도 받는다.
|
|
8
8
|
* - `flow-attachment`: 주문 흐름 행동의 첨부(시안 등, 이슈 #115). 구매자에게 보이므로 정지 이미지와 PDF만 받는다.
|
|
9
|
+
* - `ai-attachment`: 상점 플랫폼 AI 채팅에 붙이는 자료(이슈 #124). 모델이 읽는 정지 이미지와 PDF만 받는다.
|
|
9
10
|
*/
|
|
10
|
-
export const uploadPurposeSchema = z.enum([
|
|
11
|
+
export const uploadPurposeSchema = z.enum([
|
|
12
|
+
"product-image",
|
|
13
|
+
"description",
|
|
14
|
+
"flow-attachment",
|
|
15
|
+
"ai-attachment",
|
|
16
|
+
]);
|
|
11
17
|
|
|
12
18
|
export type UploadPurpose = z.infer<typeof uploadPurposeSchema>;
|
|
13
19
|
|
|
@@ -39,6 +45,11 @@ export const UPLOAD_PURPOSE_LIMITS: Record<UploadPurpose, UploadPurposeLimit> =
|
|
|
39
45
|
extensions: ["jpg", "jpeg", "png", "webp", "avif", "gif", "pdf"],
|
|
40
46
|
maxBytes: 20 * MB,
|
|
41
47
|
},
|
|
48
|
+
// 모델이 받는 형식(jpeg·png·webp·gif, PDF)만 받는다
|
|
49
|
+
"ai-attachment": {
|
|
50
|
+
extensions: ["jpg", "jpeg", "png", "webp", "gif", "pdf"],
|
|
51
|
+
maxBytes: 10 * MB,
|
|
52
|
+
},
|
|
42
53
|
};
|
|
43
54
|
|
|
44
55
|
/** 파일명의 소문자 확장자. 확장자가 없으면 `""` */
|