@sayren/store-sdk 0.30.15 → 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/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.app`) */
26
+ /** api 주소(예: `https://api.sayren.io`) */
27
27
  baseUrl: string;
28
28
  accessToken: string;
29
29
  storeId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/store-sdk",
3
- "version": "0.30.15",
3
+ "version": "0.30.17",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
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),
@@ -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.app`) */
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.app/api/auth/oauth2/authorize`) */
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",
@@ -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.app 「키 발행」) — 값은 만들 때 한 번만 보인다 */
547
+ /** 앱 배포 키(developer.sayren.io 「키 발행」) — 값은 만들 때 한 번만 보인다 */
548
548
  export const appDeployKeySchema = z.object({
549
549
  keyId: z.string(),
550
550
  name: z.string(),
@@ -549,6 +549,12 @@ export type CreateStorefrontPreviewLinkRequest = z.infer<
549
549
 
550
550
  export const storefrontPreviewLinkSchema = z.object({
551
551
  url: z.string().describe("미리보기 링크 — 5분 안에 연다"),
552
+ editorUrl: z
553
+ .string()
554
+ .optional()
555
+ .describe(
556
+ "편집 미리보기만: AI 편집 패널이 있는 미리보기 화면 링크(5분). 사이트는 그 안에 따로 뜨고 AI 자격은 사이트 코드에 닿지 않는다",
557
+ ),
552
558
  expiresAt: z.string().describe("링크 만료 시각 (ISO 8601)"),
553
559
  });
554
560
  export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
@@ -584,6 +590,7 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
584
590
  */
585
591
  export const STOREFRONT_PLATFORM_DOMAINS: readonly string[] = [
586
592
  "sayren.co",
593
+ "sayren.io",
587
594
  "sayren.app",
588
595
  "sayren.shop",
589
596
  "avarlabs.com",
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.app/api/auth/oauth2/{authorize,token,revoke}`.
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 {