@sayren/store-sdk 0.30.12 → 0.30.13
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 +562 -200
- package/dist/index.mjs +179 -6
- package/package.json +1 -1
- package/src/client.ts +16 -2
- package/src/schemas/app-manifest.ts +176 -4
- package/src/schemas/apps.ts +69 -1
|
@@ -251,6 +251,57 @@ export const APP_SCHEDULE_INTERVALS = {
|
|
|
251
251
|
"1d": 1440,
|
|
252
252
|
} as const;
|
|
253
253
|
export type AppScheduleInterval = keyof typeof APP_SCHEDULE_INTERVALS;
|
|
254
|
+
|
|
255
|
+
/** 흐름 알림 이름 — 흐름 알림 채널 `app.<handle>.<이름>`의 마지막 마디(소문자·숫자·하이픈) */
|
|
256
|
+
export const APP_FLOW_NOTIFICATION_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
|
|
257
|
+
/** 흐름 사건 이름 — 사건 키 `app.<handle>.<이름>`의 마지막 마디 */
|
|
258
|
+
export const APP_FLOW_EVENT_PATTERN = /^[A-Z][A-Z0-9_]{0,39}$/;
|
|
259
|
+
export const APP_FLOW_ITEMS_MAX = 20;
|
|
260
|
+
|
|
261
|
+
/** 흐름 알림 매개변수 — 셀러가 편집기에서 식으로 값을 연결한다 */
|
|
262
|
+
export const appFlowParamSchema = z.strictObject({
|
|
263
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
264
|
+
label: z.string().trim().min(1).max(60),
|
|
265
|
+
required: z.boolean().optional(),
|
|
266
|
+
});
|
|
267
|
+
export type AppFlowParam = z.infer<typeof appFlowParamSchema>;
|
|
268
|
+
|
|
269
|
+
export const appFlowNotificationSchema = z.strictObject({
|
|
270
|
+
label: z.string().trim().min(1).max(60),
|
|
271
|
+
description: z.string().trim().max(200).optional(),
|
|
272
|
+
params: z
|
|
273
|
+
.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,39}$/), appFlowParamSchema)
|
|
274
|
+
.refine((params) => Object.keys(params).length <= APP_FLOW_ITEMS_MAX, {
|
|
275
|
+
message: `매개변수는 ${APP_FLOW_ITEMS_MAX}개까지입니다`,
|
|
276
|
+
})
|
|
277
|
+
.default({}),
|
|
278
|
+
});
|
|
279
|
+
export type AppFlowNotification = z.infer<typeof appFlowNotificationSchema>;
|
|
280
|
+
|
|
281
|
+
export const appFlowEventSchema = z.strictObject({
|
|
282
|
+
label: z.string().trim().min(1).max(60),
|
|
283
|
+
description: z.string().trim().max(200).optional(),
|
|
284
|
+
});
|
|
285
|
+
export type AppFlowEvent = z.infer<typeof appFlowEventSchema>;
|
|
286
|
+
|
|
287
|
+
/** 에이전트 도구 이름 — 소문자·숫자·하이픈 */
|
|
288
|
+
export const APP_TOOL_NAME_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
|
|
289
|
+
export const appToolInputSchema = z.strictObject({
|
|
290
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
291
|
+
description: z.string().trim().max(200).optional(),
|
|
292
|
+
required: z.boolean().optional(),
|
|
293
|
+
});
|
|
294
|
+
export type AppToolInput = z.infer<typeof appToolInputSchema>;
|
|
295
|
+
export const appToolSchema = z.strictObject({
|
|
296
|
+
description: z.string().trim().min(1).max(500),
|
|
297
|
+
input: z
|
|
298
|
+
.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,39}$/), appToolInputSchema)
|
|
299
|
+
.refine((input) => Object.keys(input).length <= APP_FLOW_ITEMS_MAX, {
|
|
300
|
+
message: `입력은 ${APP_FLOW_ITEMS_MAX}개까지입니다`,
|
|
301
|
+
})
|
|
302
|
+
.default({}),
|
|
303
|
+
});
|
|
304
|
+
export type AppTool = z.infer<typeof appToolSchema>;
|
|
254
305
|
const APP_SCHEDULE_INTERVAL_KEYS = Object.keys(APP_SCHEDULE_INTERVALS) as [
|
|
255
306
|
AppScheduleInterval,
|
|
256
307
|
...AppScheduleInterval[],
|
|
@@ -306,8 +357,21 @@ export const appManifestSchema = z.strictObject({
|
|
|
306
357
|
.regex(APP_FOLDER_FILE)
|
|
307
358
|
.refine((path) => /\.md$/i.test(path), { message: "README는 .md 파일입니다" })
|
|
308
359
|
.optional(),
|
|
309
|
-
/** 실행 위치 —
|
|
310
|
-
runtime: z.
|
|
360
|
+
/** 실행 위치 — `code`(sayren이 실행) 또는 `external`(개발자 서버, 매뉴얼 「개발자 서버에서 실행」) */
|
|
361
|
+
runtime: z.enum(["code", "external"]).default("code"),
|
|
362
|
+
/** `runtime: external`의 호출 주소 */
|
|
363
|
+
external: z.strictObject({ url: z.url().startsWith("https://") }).optional(),
|
|
364
|
+
/** AI 에이전트 도구(매뉴얼 「에이전트 도구」) */
|
|
365
|
+
mcp: z
|
|
366
|
+
.strictObject({
|
|
367
|
+
tools: z
|
|
368
|
+
.record(z.string().regex(APP_TOOL_NAME_PATTERN), appToolSchema)
|
|
369
|
+
.refine((tools) => Object.keys(tools).length <= APP_FLOW_ITEMS_MAX, {
|
|
370
|
+
message: `도구는 ${APP_FLOW_ITEMS_MAX}개까지입니다`,
|
|
371
|
+
})
|
|
372
|
+
.default({}),
|
|
373
|
+
})
|
|
374
|
+
.optional(),
|
|
311
375
|
/** 설치 때 전체 동의하는 스코프 */
|
|
312
376
|
scopes: z.array(appScopeSchema).max(32).default([]),
|
|
313
377
|
network: z
|
|
@@ -337,6 +401,23 @@ export const appManifestSchema = z.strictObject({
|
|
|
337
401
|
message: `예약 작업은 ${APP_SCHEDULES_MAX}개까지입니다`,
|
|
338
402
|
})
|
|
339
403
|
.default({}),
|
|
404
|
+
/** 주문 흐름 연동(매뉴얼 「주문 흐름에서 앱 쓰기」) — 흐름 알림(흐름 → 앱)과 흐름 사건(앱 → 흐름) */
|
|
405
|
+
flow: z
|
|
406
|
+
.strictObject({
|
|
407
|
+
notifications: z
|
|
408
|
+
.record(z.string().regex(APP_FLOW_NOTIFICATION_PATTERN), appFlowNotificationSchema)
|
|
409
|
+
.refine((items) => Object.keys(items).length <= APP_FLOW_ITEMS_MAX, {
|
|
410
|
+
message: `흐름 알림은 ${APP_FLOW_ITEMS_MAX}개까지입니다`,
|
|
411
|
+
})
|
|
412
|
+
.default({}),
|
|
413
|
+
events: z
|
|
414
|
+
.record(z.string().regex(APP_FLOW_EVENT_PATTERN), appFlowEventSchema)
|
|
415
|
+
.refine((items) => Object.keys(items).length <= APP_FLOW_ITEMS_MAX, {
|
|
416
|
+
message: `흐름 사건은 ${APP_FLOW_ITEMS_MAX}개까지입니다`,
|
|
417
|
+
})
|
|
418
|
+
.default({}),
|
|
419
|
+
})
|
|
420
|
+
.optional(),
|
|
340
421
|
settings: z
|
|
341
422
|
.record(z.string().regex(APP_SETTING_KEY_PATTERN), appSettingFieldSchema)
|
|
342
423
|
.refine((fields) => Object.keys(fields).length <= APP_SETTINGS_MAX_FIELDS, {
|
|
@@ -369,7 +450,10 @@ export interface AppManifestProblem {
|
|
|
369
450
|
| "SELECT_OPTIONS_REQUIRED"
|
|
370
451
|
| "SECRET_FIELD_TYPE"
|
|
371
452
|
| "DEFAULT_TYPE_MISMATCH"
|
|
372
|
-
| "DUPLICATE"
|
|
453
|
+
| "DUPLICATE"
|
|
454
|
+
| "EXTERNAL_URL_REQUIRED"
|
|
455
|
+
| "EXTERNAL_NETWORK_HOSTS"
|
|
456
|
+
| "EXTERNAL_URL_INVALID";
|
|
373
457
|
message: string;
|
|
374
458
|
}
|
|
375
459
|
|
|
@@ -390,6 +474,28 @@ export function appManifestProblems(manifest: AppManifest): AppManifestProblem[]
|
|
|
390
474
|
unique(manifest.network.hosts, "network.hosts", "호스트");
|
|
391
475
|
unique(manifest.secrets, "secrets", "비밀 값 이름");
|
|
392
476
|
unique(manifest.http?.public ?? [], "http.public", "공개 HTTP 핸들러");
|
|
477
|
+
// 외부 런타임 — 주소가 있어야 하고, 내보내는 곳을 sayren이 막을 수 없어 network.hosts를 받지 않는다
|
|
478
|
+
if ((manifest.runtime === "external") !== Boolean(manifest.external)) {
|
|
479
|
+
problems.push({
|
|
480
|
+
path: "external",
|
|
481
|
+
code: "EXTERNAL_URL_REQUIRED",
|
|
482
|
+
message: "runtime이 external이면 external.url이 필요하고, code면 external을 적지 않습니다",
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (manifest.runtime === "external" && manifest.network.hosts.length > 0) {
|
|
486
|
+
problems.push({
|
|
487
|
+
path: "network.hosts",
|
|
488
|
+
code: "EXTERNAL_NETWORK_HOSTS",
|
|
489
|
+
message: "외부 런타임 앱은 network.hosts를 적지 않습니다",
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (manifest.external) {
|
|
493
|
+
const host = new URL(manifest.external.url).hostname;
|
|
494
|
+
const hostProblem = appNetworkHostProblem(host);
|
|
495
|
+
if (hostProblem) {
|
|
496
|
+
problems.push({ path: "external.url", code: "EXTERNAL_URL_INVALID", message: hostProblem });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
393
499
|
for (const event of manifest.events) {
|
|
394
500
|
if (!scopesCoverEvent(manifest.scopes, event)) {
|
|
395
501
|
problems.push({
|
|
@@ -442,6 +548,13 @@ export interface AppCapabilities {
|
|
|
442
548
|
publicHttp?: string[];
|
|
443
549
|
/** 예약 작업 이름 → 간격. 옛 버전 행에는 없다 — 읽을 때 `?? {}` */
|
|
444
550
|
schedules?: Record<string, AppScheduleInterval>;
|
|
551
|
+
/** 흐름 알림(이름 → 표시 이름·매개변수)·흐름 사건(이름 → 표시 이름). 옛 버전 행에는 없다 — 읽을 때 `?? {}` */
|
|
552
|
+
flowNotifications?: Record<string, AppFlowNotification>;
|
|
553
|
+
flowEvents?: Record<string, AppFlowEvent>;
|
|
554
|
+
/** 외부 런타임의 호출 호스트(동의 화면). 코드 앱·옛 버전 행은 없다 */
|
|
555
|
+
externalHost?: string | null;
|
|
556
|
+
/** 에이전트 도구. 옛 버전 행에는 없다 — 읽을 때 `?? {}` */
|
|
557
|
+
tools?: Record<string, AppTool>;
|
|
445
558
|
/** 필수 설정 키 — 하나라도 비어 있으면 이벤트 함수를 부르지 않는다 */
|
|
446
559
|
requiredSettings: string[];
|
|
447
560
|
secretSettings: string[];
|
|
@@ -455,6 +568,10 @@ export function appCapabilitiesOf(manifest: AppManifest): AppCapabilities {
|
|
|
455
568
|
networkHosts: sorted(manifest.network.hosts),
|
|
456
569
|
adminPage: Boolean(manifest.ui?.adminPage),
|
|
457
570
|
publicHttp: sorted(manifest.http?.public ?? []),
|
|
571
|
+
flowNotifications: manifest.flow?.notifications ?? {},
|
|
572
|
+
flowEvents: manifest.flow?.events ?? {},
|
|
573
|
+
externalHost: manifest.external ? new URL(manifest.external.url).hostname : null,
|
|
574
|
+
tools: manifest.mcp?.tools ?? {},
|
|
458
575
|
schedules: Object.fromEntries(
|
|
459
576
|
Object.entries(manifest.schedules)
|
|
460
577
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
@@ -476,7 +593,7 @@ export function appCapabilitiesOf(manifest: AppManifest): AppCapabilities {
|
|
|
476
593
|
* 동의 문단은 accounts 설치 화면·상점 플랫폼 앱 상세·유저 문서가 같은 문장을 쓴다.
|
|
477
594
|
*/
|
|
478
595
|
export interface AppCapabilityDefinition {
|
|
479
|
-
key: "scopes" | "network" | "events" | "ui" | "publicHttp" | "schedules";
|
|
596
|
+
key: "scopes" | "network" | "events" | "ui" | "publicHttp" | "schedules" | "runtime" | "tools";
|
|
480
597
|
/** 동의 화면의 문단 제목 */
|
|
481
598
|
title: string;
|
|
482
599
|
/** 동의 화면에 보일 줄 — 없으면 문단을 그리지 않는다 */
|
|
@@ -545,6 +662,19 @@ export const APP_CAPABILITIES: readonly AppCapabilityDefinition[] = [
|
|
|
545
662
|
);
|
|
546
663
|
},
|
|
547
664
|
},
|
|
665
|
+
{
|
|
666
|
+
key: "runtime",
|
|
667
|
+
title: "이 앱이 실행되는 곳",
|
|
668
|
+
describe: (c) => (c.externalHost ? [`개발자 서버 ${c.externalHost}`] : []),
|
|
669
|
+
widens: (p, n) => Boolean(n.externalHost) && n.externalHost !== p.externalHost,
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
key: "tools",
|
|
673
|
+
title: "AI 에이전트 도구",
|
|
674
|
+
describe: (c) =>
|
|
675
|
+
Object.entries(c.tools ?? {}).map(([name, tool]) => `${name} · ${tool.description}`),
|
|
676
|
+
widens: (p, n) => grew(Object.keys(p.tools ?? {}), Object.keys(n.tools ?? {})),
|
|
677
|
+
},
|
|
548
678
|
];
|
|
549
679
|
|
|
550
680
|
/** 요청(메서드·경로)이 HTTP 핸들러 키에 맞는가 — `:이름`은 경로 한 마디에 맞는다(`@sayren/app`의 핸들러 고르기와 같은 규칙) */
|
|
@@ -567,6 +697,48 @@ export function appScheduleListOf(
|
|
|
567
697
|
.map(([name, every]) => ({ name, every }));
|
|
568
698
|
}
|
|
569
699
|
|
|
700
|
+
/** 응답용 흐름 알림·사건 목록(이름순) — 옛 버전 행은 빈 목록 */
|
|
701
|
+
export function appFlowSummaryOf(
|
|
702
|
+
capabilities: Pick<AppCapabilities, "flowNotifications" | "flowEvents">,
|
|
703
|
+
): {
|
|
704
|
+
flowNotifications: Array<{
|
|
705
|
+
name: string;
|
|
706
|
+
label: string;
|
|
707
|
+
params: Array<{ name: string; type: AppFlowParam["type"]; label: string; required: boolean }>;
|
|
708
|
+
}>;
|
|
709
|
+
flowEvents: Array<{ name: string; label: string }>;
|
|
710
|
+
} {
|
|
711
|
+
const byName = <T>(entries: [string, T][]) =>
|
|
712
|
+
entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
713
|
+
return {
|
|
714
|
+
flowNotifications: byName(Object.entries(capabilities.flowNotifications ?? {})).map(
|
|
715
|
+
([name, notice]) => ({
|
|
716
|
+
name,
|
|
717
|
+
label: notice.label,
|
|
718
|
+
params: byName(Object.entries(notice.params ?? {})).map(([param, field]) => ({
|
|
719
|
+
name: param,
|
|
720
|
+
type: field.type,
|
|
721
|
+
label: field.label,
|
|
722
|
+
required: field.required ?? false,
|
|
723
|
+
})),
|
|
724
|
+
}),
|
|
725
|
+
),
|
|
726
|
+
flowEvents: byName(Object.entries(capabilities.flowEvents ?? {})).map(([name, event]) => ({
|
|
727
|
+
name,
|
|
728
|
+
label: event.label,
|
|
729
|
+
})),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** 응답용 도구 목록(이름순) — 옛 버전 행은 빈 목록 */
|
|
734
|
+
export function appToolListOf(
|
|
735
|
+
capabilities: Pick<AppCapabilities, "tools">,
|
|
736
|
+
): Array<{ name: string; description: string }> {
|
|
737
|
+
return Object.entries(capabilities.tools ?? {})
|
|
738
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
739
|
+
.map(([name, tool]) => ({ name, description: tool.description }));
|
|
740
|
+
}
|
|
741
|
+
|
|
570
742
|
/** 예약 간격의 표시 이름 — 동의 화면·상점 플랫폼이 같이 쓴다 */
|
|
571
743
|
export function appScheduleIntervalLabel(every: AppScheduleInterval): string {
|
|
572
744
|
const minutes = APP_SCHEDULE_INTERVALS[every];
|
package/src/schemas/apps.ts
CHANGED
|
@@ -76,11 +76,38 @@ const publicHttpField = z
|
|
|
76
76
|
.array(z.string())
|
|
77
77
|
.default([])
|
|
78
78
|
.describe("외부에서 부를 수 있는 HTTP 핸들러(`GET /track/:id`)");
|
|
79
|
+
/** 흐름 알림·사건(매뉴얼 「주문 흐름에서 앱 쓰기」) — 흐름 편집기가 알림 채널·매개변수와 사건 이름을 고른다 */
|
|
80
|
+
export const appFlowNotificationSummarySchema = z.object({
|
|
81
|
+
name: z.string(),
|
|
82
|
+
label: z.string(),
|
|
83
|
+
params: z.array(
|
|
84
|
+
z.object({
|
|
85
|
+
name: z.string(),
|
|
86
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
87
|
+
label: z.string(),
|
|
88
|
+
required: z.boolean(),
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
});
|
|
92
|
+
export type AppFlowNotificationSummary = z.infer<typeof appFlowNotificationSummarySchema>;
|
|
93
|
+
export const appFlowEventSummarySchema = z.object({ name: z.string(), label: z.string() });
|
|
94
|
+
export type AppFlowEventSummary = z.infer<typeof appFlowEventSummarySchema>;
|
|
95
|
+
|
|
79
96
|
const schedulesField = z
|
|
80
97
|
.array(appScheduleSummarySchema)
|
|
81
98
|
.default([])
|
|
82
99
|
.describe("주기적으로 실행되는 작업");
|
|
83
100
|
|
|
101
|
+
const toolsField = z
|
|
102
|
+
.array(z.object({ name: z.string(), description: z.string() }))
|
|
103
|
+
.default([])
|
|
104
|
+
.describe("AI 에이전트 도구");
|
|
105
|
+
const externalHostField = z
|
|
106
|
+
.string()
|
|
107
|
+
.nullable()
|
|
108
|
+
.default(null)
|
|
109
|
+
.describe("개발자 서버에서 실행하는 앱의 호출 호스트. sayren이 실행하면 null");
|
|
110
|
+
|
|
84
111
|
export const appInstallationSchema = z.object({
|
|
85
112
|
installationId: z.string(),
|
|
86
113
|
storeId: z.string(),
|
|
@@ -100,6 +127,8 @@ export const appInstallationSchema = z.object({
|
|
|
100
127
|
events: z.array(appEventTypeSchema),
|
|
101
128
|
publicHttp: publicHttpField,
|
|
102
129
|
schedules: schedulesField,
|
|
130
|
+
tools: toolsField,
|
|
131
|
+
externalHost: externalHostField,
|
|
103
132
|
})
|
|
104
133
|
.nullable()
|
|
105
134
|
.optional()
|
|
@@ -110,6 +139,16 @@ export const appInstallationSchema = z.object({
|
|
|
110
139
|
adminPage: z.boolean().describe("상점 플랫폼 앱 화면이 있는가"),
|
|
111
140
|
publicHttp: publicHttpField,
|
|
112
141
|
schedules: schedulesField,
|
|
142
|
+
tools: toolsField,
|
|
143
|
+
externalHost: externalHostField,
|
|
144
|
+
flowNotifications: z
|
|
145
|
+
.array(appFlowNotificationSummarySchema)
|
|
146
|
+
.default([])
|
|
147
|
+
.describe("주문 흐름에서 이 앱으로 보낼 수 있는 알림"),
|
|
148
|
+
flowEvents: z
|
|
149
|
+
.array(appFlowEventSummarySchema)
|
|
150
|
+
.default([])
|
|
151
|
+
.describe("이 앱이 주문 흐름에 보내는 사건(`app.<handle>.<이름>`)"),
|
|
113
152
|
/** 설치 결과의 캐시가 아니라 실행 버전 매니페스트의 요약 */
|
|
114
153
|
settings: appInstallationSettingsSchema,
|
|
115
154
|
installedBy: z.object({ userId: z.string(), name: z.string().nullable() }).nullable(),
|
|
@@ -197,6 +236,8 @@ export const storeAppDetailSchema = storeAppSchema.extend({
|
|
|
197
236
|
networkHosts: z.array(z.string()).describe("앱이 데이터를 보내는 외부 호스트"),
|
|
198
237
|
publicHttp: publicHttpField,
|
|
199
238
|
schedules: schedulesField,
|
|
239
|
+
tools: toolsField,
|
|
240
|
+
externalHost: externalHostField,
|
|
200
241
|
settings: z.record(z.string(), appSettingFieldSchema).describe("설치 때 채우는 설정 필드"),
|
|
201
242
|
updatedAt: z.string().nullable().describe("현재 버전을 release한 시각"),
|
|
202
243
|
});
|
|
@@ -222,6 +263,33 @@ export const appInstallationSelfSchema = z.object({
|
|
|
222
263
|
});
|
|
223
264
|
export type AppInstallationSelf = z.infer<typeof appInstallationSelfSchema>;
|
|
224
265
|
|
|
266
|
+
/** 에이전트 도구(매뉴얼 「에이전트 도구」) — `GET /v1/apps/tools` 한 줄 */
|
|
267
|
+
export const appToolSummarySchema = z.object({
|
|
268
|
+
installationId: z.string(),
|
|
269
|
+
app: z.object({ appId: z.string(), handle: z.string(), name: z.string() }),
|
|
270
|
+
name: z.string(),
|
|
271
|
+
description: z.string(),
|
|
272
|
+
input: z.array(
|
|
273
|
+
z.object({
|
|
274
|
+
name: z.string(),
|
|
275
|
+
type: z.enum(["string", "number", "boolean"]),
|
|
276
|
+
description: z.string().nullable(),
|
|
277
|
+
required: z.boolean(),
|
|
278
|
+
}),
|
|
279
|
+
),
|
|
280
|
+
});
|
|
281
|
+
export type AppToolSummary = z.infer<typeof appToolSummarySchema>;
|
|
282
|
+
|
|
283
|
+
export const appToolCallSchema = z.object({
|
|
284
|
+
input: z.record(z.string(), z.unknown()).default({}),
|
|
285
|
+
});
|
|
286
|
+
export type AppToolCall = z.input<typeof appToolCallSchema>;
|
|
287
|
+
|
|
288
|
+
export const appToolResultSchema = z.object({
|
|
289
|
+
result: z.unknown().describe("앱 도구가 돌려준 값(JSON)"),
|
|
290
|
+
});
|
|
291
|
+
export type AppToolResult = z.infer<typeof appToolResultSchema>;
|
|
292
|
+
|
|
225
293
|
/** 설치 저장소(`ctx.storage`, 매뉴얼 「앱 코드 › 저장소」) — 설치 토큰 전용 */
|
|
226
294
|
export const APP_STORAGE_KEY_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
|
|
227
295
|
export const APP_STORAGE_MAX_KEYS = 1_000;
|
|
@@ -464,7 +532,7 @@ export const appInvocationSchema = z.object({
|
|
|
464
532
|
storeName: z.string().nullable(),
|
|
465
533
|
installationId: z.string(),
|
|
466
534
|
versionNumber: z.number().int().nullable(),
|
|
467
|
-
kind: z.enum(["event", "http", "schedule"]),
|
|
535
|
+
kind: z.enum(["event", "http", "schedule", "flow", "tool"]),
|
|
468
536
|
eventType: z.string().nullable(),
|
|
469
537
|
outcome: z.enum(["OK", "ERROR", "LIMIT_EXCEEDED", "REJECTED"]),
|
|
470
538
|
errorMessage: z.string().nullable(),
|