@sayren/store-sdk 0.30.16 → 0.30.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.
- package/dist/index.d.mts +737 -209
- package/dist/index.mjs +7847 -7724
- package/dist/server.d.mts +1 -1
- package/package.json +1 -1
- package/src/client.ts +3 -0
- package/src/member-delegation.ts +1 -1
- package/src/oauth.ts +11 -1
- package/src/schemas/ai.ts +98 -0
- package/src/schemas/app-manifest.ts +1 -0
- package/src/schemas/apps.ts +1 -1
- package/src/schemas/storefront-hosting.ts +1 -0
- package/src/session.ts +1 -1
package/dist/server.d.mts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class MemberDelegationError extends Error {
|
|
|
23
23
|
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
24
24
|
*/
|
|
25
25
|
export declare function delegateMember(options: {
|
|
26
|
-
/** api 주소(예: `https://api.sayren.
|
|
26
|
+
/** api 주소(예: `https://api.sayren.io`) */
|
|
27
27
|
baseUrl: string;
|
|
28
28
|
accessToken: string;
|
|
29
29
|
storeId: string;
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -284,6 +284,7 @@ import {
|
|
|
284
284
|
savedOptionSetRequestSchema,
|
|
285
285
|
savedOptionSetSchema,
|
|
286
286
|
} from "./schemas/saved-option-set";
|
|
287
|
+
import { starterStatusSchema } from "./schemas/starter";
|
|
287
288
|
import {
|
|
288
289
|
calendarDaySchema,
|
|
289
290
|
calendarQuerySchema,
|
|
@@ -1860,6 +1861,8 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1860
1861
|
storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, {
|
|
1861
1862
|
body: createStorefrontPreviewLinkRequestSchema.parse(body),
|
|
1862
1863
|
}),
|
|
1864
|
+
/** `GET /v1/storefront/starter` — 쇼핑몰 만들기 진행 상태(서버 상태로 계산한 단계·셀러 항목, 공개 문서 제외) */
|
|
1865
|
+
getStarterStatus: () => storeHttp.request("GET", "/storefront/starter", starterStatusSchema),
|
|
1863
1866
|
/** `GET /v1/storefront/site/domains` — 주소 목록(기본 주소 + 내 도메인)과 DNS 안내 */
|
|
1864
1867
|
listDomains: () =>
|
|
1865
1868
|
storeHttp.request("GET", "/storefront/site/domains", storefrontSiteDomainsSchema),
|
package/src/member-delegation.ts
CHANGED
|
@@ -28,7 +28,7 @@ export class MemberDelegationError extends Error {
|
|
|
28
28
|
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
29
29
|
*/
|
|
30
30
|
export async function delegateMember(options: {
|
|
31
|
-
/** api 주소(예: `https://api.sayren.
|
|
31
|
+
/** api 주소(예: `https://api.sayren.io`) */
|
|
32
32
|
baseUrl: string;
|
|
33
33
|
accessToken: string;
|
|
34
34
|
storeId: string;
|
package/src/oauth.ts
CHANGED
|
@@ -30,7 +30,7 @@ export async function createPkcePair(): Promise<{ verifier: string; challenge: s
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export interface AuthorizeUrlOptions {
|
|
33
|
-
/** authorize 엔드포인트 전체 주소(예: `https://accounts.sayren.
|
|
33
|
+
/** authorize 엔드포인트 전체 주소(예: `https://accounts.sayren.io/api/auth/oauth2/authorize`) */
|
|
34
34
|
authorizeUrl: string;
|
|
35
35
|
clientId: string;
|
|
36
36
|
redirectUri: string;
|
|
@@ -59,6 +59,12 @@ export function buildAuthorizeUrl(options: AuthorizeUrlOptions): string {
|
|
|
59
59
|
return url.toString();
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 토큰 요청 상한 — 응답 없는 요청이 끝나지 않으면 refresh가 탭 간 잠금(`sayren.store.refresh`)을 쥔 채 멈추고, 그 잠금을 기다리는 모든 API 호출이
|
|
64
|
+
* 같이 멈춘다(2026-10-12 상점 만들기 버튼이 진행 표시로 굳었다). 넘으면 실패로 끝낸다
|
|
65
|
+
*/
|
|
66
|
+
export const TOKEN_REQUEST_TIMEOUT_MS = 15_000;
|
|
67
|
+
|
|
62
68
|
async function tokenRequest(
|
|
63
69
|
endpoint: string,
|
|
64
70
|
form: Record<string, string>,
|
|
@@ -69,6 +75,10 @@ async function tokenRequest(
|
|
|
69
75
|
method: "POST",
|
|
70
76
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
71
77
|
body: new URLSearchParams(form).toString(),
|
|
78
|
+
// AbortSignal.timeout이 없는 런타임은 제한 없이 보낸다
|
|
79
|
+
...(typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
|
|
80
|
+
? { signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS) }
|
|
81
|
+
: {}),
|
|
72
82
|
});
|
|
73
83
|
const json: unknown = await response.json().catch(() => null);
|
|
74
84
|
if (!response.ok) {
|
package/src/schemas/ai.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { storefrontSlugSchema } from "./storefront-hosting";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 상점 플랫폼 AI 채팅(이슈 #124) — 대화·메시지·실행과 실행 이벤트 스트림의 계약.
|
|
@@ -72,13 +73,62 @@ export const aiRunSchema = z.object({
|
|
|
72
73
|
});
|
|
73
74
|
export type AiRun = z.infer<typeof aiRunSchema>;
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* AI가 지금 하는 일의 종류 — 도구 이름·operationId·파일 경로로 서버가 정한다(상점 만들기 진행 화면이 아이콘·문구를 고른다). 정하지 못한 도구는 없다
|
|
78
|
+
*/
|
|
79
|
+
export const aiActivitySchema = z.enum([
|
|
80
|
+
"status",
|
|
81
|
+
"brief",
|
|
82
|
+
"template",
|
|
83
|
+
"site",
|
|
84
|
+
"info",
|
|
85
|
+
"shipping",
|
|
86
|
+
"upload",
|
|
87
|
+
"design",
|
|
88
|
+
"category",
|
|
89
|
+
"product",
|
|
90
|
+
"files",
|
|
91
|
+
"code",
|
|
92
|
+
"check",
|
|
93
|
+
"verify",
|
|
94
|
+
"version",
|
|
95
|
+
"preview",
|
|
96
|
+
]);
|
|
97
|
+
export type AiActivity = z.infer<typeof aiActivitySchema>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 도구가 바꾼 파일 하나(2026-10-13, AI 상점 도우미 작업 공간의 수정 중인 코드) — 서버가 도구 입력으로 계산한다.
|
|
101
|
+
* `edit`는 바꾼 부분의 줄 비교, `write`는 새로 쓴 내용(이전 내용을 모르므로 모두 추가로 센다). `preview`는 바뀐 줄 앞부분 12줄까지다
|
|
102
|
+
*/
|
|
103
|
+
export const aiToolChangeSchema = z.object({
|
|
104
|
+
path: z.string().max(300),
|
|
105
|
+
mode: z.enum(["edit", "write"]),
|
|
106
|
+
added: z.number().int().min(0),
|
|
107
|
+
removed: z.number().int().min(0),
|
|
108
|
+
preview: z
|
|
109
|
+
.array(z.object({ kind: z.enum(["added", "removed"]), text: z.string().max(300) }))
|
|
110
|
+
.max(12),
|
|
111
|
+
});
|
|
112
|
+
export type AiToolChange = z.infer<typeof aiToolChangeSchema>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 에이전트 작업 단계(2026-10-13, AI 상점 도우미 작업 공간) — 에이전트가 패널 도구로 알린다.
|
|
116
|
+
* 상점·상품·배포 준비 → 쇼핑몰 구조 설계 → 개발 환경 준비(라이브러리·SDK) → 코드 수정 루프 → 완료
|
|
117
|
+
*/
|
|
118
|
+
export const AI_AGENT_PHASES = ["STORE", "STRUCTURE", "ENVIRONMENT", "BUILD", "DONE"] as const;
|
|
119
|
+
export const aiAgentPhaseSchema = z.enum(AI_AGENT_PHASES);
|
|
120
|
+
export type AiAgentPhase = z.infer<typeof aiAgentPhaseSchema>;
|
|
121
|
+
|
|
75
122
|
/**
|
|
76
123
|
* 화면에 그리는 메시지 조각. 모델 원문(tool_use 블록 등)은 api가 보관하고 화면에는 이 형태만 준다.
|
|
77
124
|
* - `text`: 글
|
|
78
125
|
* - `tool`: 도구 호출 한 번(이름은 사람이 읽는 라벨, 결과 요약·링크)
|
|
79
126
|
* - `choices`: 선택지 버튼(가장 최근 것만 누를 수 있다)
|
|
127
|
+
* - `business_form`: 사업자 정보 입력 폼(가장 최근 것만 제출할 수 있다, 건너뛸 수 있다)
|
|
80
128
|
* - `attachment`: 사용자가 올린 첨부
|
|
81
129
|
* - `approval`: 승인 대기로 남은 쓰기
|
|
130
|
+
* - `reasoning`: 모델의 생각 요약(2026-10-13, AI 상점 도우미 작업 공간이 도구 호출과 따로 표시한다)
|
|
131
|
+
* - `phase`: 에이전트가 알린 작업 단계(작업 공간의 단계 표시)
|
|
82
132
|
*/
|
|
83
133
|
export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
84
134
|
z.object({ type: z.literal("text"), text: z.string() }),
|
|
@@ -89,6 +139,12 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
|
89
139
|
label: z.string(),
|
|
90
140
|
status: z.enum(["ok", "error", "unknown"]),
|
|
91
141
|
summary: z.string().nullable(),
|
|
142
|
+
/** 하는 일의 종류(`aiActivitySchema`, 정하지 못했거나 옛 메시지면 없다) */
|
|
143
|
+
activity: aiActivitySchema.optional(),
|
|
144
|
+
/** 도구 대상 — 파일 경로나 검색어(파일 도구만) */
|
|
145
|
+
target: z.string().max(300).optional(),
|
|
146
|
+
/** 바꾼 파일(파일 쓰기 도구만) */
|
|
147
|
+
changes: z.array(aiToolChangeSchema).max(10).optional(),
|
|
92
148
|
}),
|
|
93
149
|
z.object({
|
|
94
150
|
type: z.literal("choices"),
|
|
@@ -98,11 +154,14 @@ export const aiMessagePartSchema = z.discriminatedUnion("type", [
|
|
|
98
154
|
.min(1)
|
|
99
155
|
.max(6),
|
|
100
156
|
}),
|
|
157
|
+
z.object({ type: z.literal("business_form"), question: z.string() }),
|
|
101
158
|
z.object({ type: z.literal("attachment"), attachment: aiAttachmentSchema }),
|
|
102
159
|
/** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다(셀러가 승인함에서 결정한다) */
|
|
103
160
|
z.object({ type: z.literal("approval"), approvalId: z.string(), title: z.string() }),
|
|
104
161
|
/** 사용자가 미리보기에서 고른 요소(#65) */
|
|
105
162
|
z.object({ type: z.literal("selection"), selection: aiPreviewSelectionSchema }),
|
|
163
|
+
z.object({ type: z.literal("reasoning"), text: z.string() }),
|
|
164
|
+
z.object({ type: z.literal("phase"), phase: aiAgentPhaseSchema }),
|
|
106
165
|
]);
|
|
107
166
|
export type AiMessagePart = z.infer<typeof aiMessagePartSchema>;
|
|
108
167
|
|
|
@@ -132,9 +191,38 @@ export const aiConversationSchema = aiConversationSummarySchema.extend({
|
|
|
132
191
|
});
|
|
133
192
|
export type AiConversation = z.infer<typeof aiConversationSchema>;
|
|
134
193
|
|
|
194
|
+
/** 쇼핑몰 형태(#124, 2026-10-13) — 템플릿은 basic 하나이고 형태는 에이전트에게 주는 화면 구성 지시다 */
|
|
195
|
+
export const STORE_SHOP_TYPES = [
|
|
196
|
+
"BASIC",
|
|
197
|
+
"INTERACTIVE",
|
|
198
|
+
"STORYTELLING",
|
|
199
|
+
"MOBILE",
|
|
200
|
+
"CATALOG",
|
|
201
|
+
"LOOKBOOK",
|
|
202
|
+
] as const;
|
|
203
|
+
export const storeShopTypeSchema = z.enum(STORE_SHOP_TYPES);
|
|
204
|
+
export type StoreShopType = z.infer<typeof storeShopTypeSchema>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* AI 상점 만들기 질문 화면의 답(#124, 2026-10-13) — 화면이 고정 질문으로 모은다. 초안과 함께 첫 메시지가 된다.
|
|
208
|
+
* 배송비는 묻지 않는다(설정하지 않으면 무료배송). `slug`는 바라는 주소이고 쓸 수 있는지는 에이전트가 사이트를 만들 때 확인한다
|
|
209
|
+
*/
|
|
210
|
+
export const storeSetupBriefSchema = z.object({
|
|
211
|
+
products: z.string().trim().min(1).max(500).describe("판매 상품"),
|
|
212
|
+
storeName: z.string().trim().min(1).max(50).describe("쇼핑몰 이름"),
|
|
213
|
+
audience: z.string().trim().min(1).max(300).describe("주요 고객"),
|
|
214
|
+
highlights: z.string().trim().max(500).describe("강조할 특징 (비우면 AI가 정한다)"),
|
|
215
|
+
shopType: storeShopTypeSchema.describe("쇼핑몰 형태"),
|
|
216
|
+
heroProduct: z.string().trim().max(500).describe("첫 화면에 크게 걸 상품 (비우면 AI가 정한다)"),
|
|
217
|
+
slug: storefrontSlugSchema.optional().describe("바라는 스토어프론트 주소"),
|
|
218
|
+
});
|
|
219
|
+
export type StoreSetupBrief = z.infer<typeof storeSetupBriefSchema>;
|
|
220
|
+
|
|
135
221
|
export const createAiConversationRequestSchema = z.object({
|
|
136
222
|
/** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
|
|
137
223
|
draftId: z.string().max(64).optional(),
|
|
224
|
+
/** 질문 화면의 답 — `draftId`와 함께만 쓴다. 초안 앞에 붙어 첫 메시지가 된다 */
|
|
225
|
+
brief: storeSetupBriefSchema.optional(),
|
|
138
226
|
});
|
|
139
227
|
export type CreateAiConversationRequest = z.infer<typeof createAiConversationRequestSchema>;
|
|
140
228
|
|
|
@@ -279,6 +367,10 @@ export type AiRunRevertConflictDetails = z.infer<typeof aiRunRevertConflictDetai
|
|
|
279
367
|
export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
280
368
|
/** 글 조각(약 100ms 묶음). 같은 `messageId`의 `text`에 이어 붙인다 */
|
|
281
369
|
z.object({ type: z.literal("delta"), messageId: z.string(), text: z.string() }),
|
|
370
|
+
/** 생각 요약 조각 — 같은 `messageId`의 생각에 이어 붙인다(글과 따로 표시한다) */
|
|
371
|
+
z.object({ type: z.literal("reasoning_delta"), messageId: z.string(), text: z.string() }),
|
|
372
|
+
/** 에이전트가 작업 단계를 알렸다 */
|
|
373
|
+
z.object({ type: z.literal("phase"), messageId: z.string(), phase: aiAgentPhaseSchema }),
|
|
282
374
|
z.object({
|
|
283
375
|
type: z.literal("tool_start"),
|
|
284
376
|
messageId: z.string(),
|
|
@@ -287,6 +379,10 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
287
379
|
label: z.string(),
|
|
288
380
|
/** 쓰기 도구가 바꾸는 화면(읽기 도구는 없다) */
|
|
289
381
|
focus: aiFocusSchema.optional(),
|
|
382
|
+
/** 하는 일의 종류(정하지 못한 도구는 없다) */
|
|
383
|
+
activity: aiActivitySchema.optional(),
|
|
384
|
+
target: z.string().max(300).optional(),
|
|
385
|
+
changes: z.array(aiToolChangeSchema).max(10).optional(),
|
|
290
386
|
}),
|
|
291
387
|
z.object({
|
|
292
388
|
type: z.literal("tool_result"),
|
|
@@ -301,6 +397,8 @@ export const aiStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
301
397
|
question: z.string(),
|
|
302
398
|
options: z.array(z.object({ label: z.string(), value: z.string() })),
|
|
303
399
|
}),
|
|
400
|
+
/** 사업자 정보 입력 폼을 보였다 — 답은 다음 메시지로 온다 */
|
|
401
|
+
z.object({ type: z.literal("business_form"), messageId: z.string(), question: z.string() }),
|
|
304
402
|
/** 공개 뒤 승인이 필요한 쓰기를 승인 요청으로 남겼다 */
|
|
305
403
|
z.object({
|
|
306
404
|
type: z.literal("approval"),
|
|
@@ -170,6 +170,7 @@ export function scopesCoverEvent(scopes: readonly string[], event: AppEventType)
|
|
|
170
170
|
const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
171
171
|
const TLD = /^(?:[a-z]{2,63}|xn--[a-z0-9-]{1,59})$/;
|
|
172
172
|
export const APP_NETWORK_DENIED_SUFFIXES: readonly string[] = [
|
|
173
|
+
"sayren.io",
|
|
173
174
|
"sayren.app",
|
|
174
175
|
"sayren.co",
|
|
175
176
|
"avarlabs.com",
|
package/src/schemas/apps.ts
CHANGED
|
@@ -544,7 +544,7 @@ export const appInvocationSchema = z.object({
|
|
|
544
544
|
});
|
|
545
545
|
export type AppInvocation = z.infer<typeof appInvocationSchema>;
|
|
546
546
|
|
|
547
|
-
/** 앱 배포 키(developer.sayren.
|
|
547
|
+
/** 앱 배포 키(developer.sayren.io 「키 발행」) — 값은 만들 때 한 번만 보인다 */
|
|
548
548
|
export const appDeployKeySchema = z.object({
|
|
549
549
|
keyId: z.string(),
|
|
550
550
|
name: z.string(),
|
package/src/session.ts
CHANGED
|
@@ -39,7 +39,7 @@ const REVOKE_TIMEOUT_MS = 5_000;
|
|
|
39
39
|
const REFRESH_LOCK = STORE_SESSION_KEYS.refresh;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* 인가 서버(Sayren 계정, accounts) 주소 — `https://accounts.sayren.
|
|
42
|
+
* 인가 서버(Sayren 계정, accounts) 주소 — `https://accounts.sayren.io/api/auth/oauth2/{authorize,token,revoke}`.
|
|
43
43
|
* `resource`를 주면 authorize·code 교환·refresh에 RFC 8707 `resource`를 싣는다(JWT access token의 `aud`).
|
|
44
44
|
*/
|
|
45
45
|
export interface StoreAuthServer {
|