dsh-convert-core 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/media-to-md.mjs +168 -0
- package/bin/tita-convert.mjs +85 -0
- package/docs/media-to-md.md +143 -0
- package/lib/adapters/anydoc.d.ts +73 -0
- package/lib/adapters/anydoc.d.ts.map +1 -0
- package/lib/adapters/anydoc.js +295 -0
- package/lib/adapters/anydoc.js.map +1 -0
- package/lib/adapters/iddoc.d.ts +28 -0
- package/lib/adapters/iddoc.d.ts.map +1 -0
- package/lib/adapters/iddoc.js +153 -0
- package/lib/adapters/iddoc.js.map +1 -0
- package/lib/adapters/index.d.ts +41 -0
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +115 -0
- package/lib/adapters/index.js.map +1 -0
- package/lib/adapters/scanned.d.ts +28 -0
- package/lib/adapters/scanned.d.ts.map +1 -0
- package/lib/adapters/scanned.js +121 -0
- package/lib/adapters/scanned.js.map +1 -0
- package/lib/adapters/text.d.ts +29 -0
- package/lib/adapters/text.d.ts.map +1 -0
- package/lib/adapters/text.js +135 -0
- package/lib/adapters/text.js.map +1 -0
- package/lib/adapters/types.d.ts +65 -0
- package/lib/adapters/types.d.ts.map +1 -0
- package/lib/adapters/types.js +11 -0
- package/lib/adapters/types.js.map +1 -0
- package/lib/contract.d.ts +185 -0
- package/lib/contract.d.ts.map +1 -0
- package/lib/contract.js +59 -0
- package/lib/contract.js.map +1 -0
- package/lib/delivery.d.ts +36 -0
- package/lib/delivery.d.ts.map +1 -0
- package/lib/delivery.js +97 -0
- package/lib/delivery.js.map +1 -0
- package/lib/http.d.ts +34 -0
- package/lib/http.d.ts.map +1 -0
- package/lib/http.js +372 -0
- package/lib/http.js.map +1 -0
- package/lib/markdown.d.ts +32 -0
- package/lib/markdown.d.ts.map +1 -0
- package/lib/markdown.js +145 -0
- package/lib/markdown.js.map +1 -0
- package/lib/media/binaries.d.ts +26 -0
- package/lib/media/binaries.d.ts.map +1 -0
- package/lib/media/binaries.js +60 -0
- package/lib/media/binaries.js.map +1 -0
- package/lib/media/convert.d.ts +19 -0
- package/lib/media/convert.d.ts.map +1 -0
- package/lib/media/convert.js +210 -0
- package/lib/media/convert.js.map +1 -0
- package/lib/media/index.d.ts +27 -0
- package/lib/media/index.d.ts.map +1 -0
- package/lib/media/index.js +27 -0
- package/lib/media/index.js.map +1 -0
- package/lib/media/markdown.d.ts +40 -0
- package/lib/media/markdown.d.ts.map +1 -0
- package/lib/media/markdown.js +89 -0
- package/lib/media/markdown.js.map +1 -0
- package/lib/media/media.d.ts +45 -0
- package/lib/media/media.d.ts.map +1 -0
- package/lib/media/media.js +167 -0
- package/lib/media/media.js.map +1 -0
- package/lib/media/models.d.ts +31 -0
- package/lib/media/models.d.ts.map +1 -0
- package/lib/media/models.js +87 -0
- package/lib/media/models.js.map +1 -0
- package/lib/media/scanned.d.ts +18 -0
- package/lib/media/scanned.d.ts.map +1 -0
- package/lib/media/scanned.js +179 -0
- package/lib/media/scanned.js.map +1 -0
- package/lib/media/types.d.ts +154 -0
- package/lib/media/types.d.ts.map +1 -0
- package/lib/media/types.js +23 -0
- package/lib/media/types.js.map +1 -0
- package/lib/server.d.ts +35 -0
- package/lib/server.d.ts.map +1 -0
- package/lib/server.js +64 -0
- package/lib/server.js.map +1 -0
- package/lib/service.d.ts +170 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +562 -0
- package/lib/service.js.map +1 -0
- package/package.json +59 -0
- package/scripts/asr.py +55 -0
- package/scripts/ocr.py +40 -0
- package/scripts/pdf_pages.py +49 -0
- package/scripts/vendor.mjs +76 -0
- package/src/adapters/anydoc.ts +356 -0
- package/src/adapters/iddoc.ts +171 -0
- package/src/adapters/index.ts +147 -0
- package/src/adapters/scanned.ts +139 -0
- package/src/adapters/text.ts +146 -0
- package/src/adapters/types.ts +76 -0
- package/src/contract.ts +230 -0
- package/src/delivery.ts +124 -0
- package/src/http.ts +394 -0
- package/src/markdown.ts +141 -0
- package/src/media/binaries.ts +67 -0
- package/src/media/convert.ts +232 -0
- package/src/media/index.ts +43 -0
- package/src/media/markdown.ts +105 -0
- package/src/media/media.ts +204 -0
- package/src/media/models.ts +124 -0
- package/src/media/scanned.ts +200 -0
- package/src/media/types.ts +171 -0
- package/src/server.ts +85 -0
- package/src/service.ts +639 -0
- package/web/app.js +382 -0
- package/web/index.html +217 -0
- package/web/styles.css +263 -0
- package/web/vendor/icons.js +25 -0
- package/web/vendor/vue.global.prod.js +14 -0
package/src/contract.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-level contract for the conversion kernel.
|
|
3
|
+
*
|
|
4
|
+
* The kernel owns exactly one promise: given an input, produce a
|
|
5
|
+
* *knowledge-ready* Markdown bundle (`index.md` + `assets/` + `manifest.json`)
|
|
6
|
+
* plus an `ingest` payload that plugins/dsh-data-domain-knowledge can consume
|
|
7
|
+
* without the two ever importing each other.
|
|
8
|
+
*
|
|
9
|
+
* Boundary rule (issue 0013): the kernel never decides `source_type`. It only
|
|
10
|
+
* proposes candidates; the ingestion side decides and records
|
|
11
|
+
* `source_type_source`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `source_format` values from the 0013 contract. The full vocabulary is kept so
|
|
16
|
+
* the front matter stays wire-compatible, but this version only ever emits
|
|
17
|
+
* `markdown`, `text`, `pdf` and `office`.
|
|
18
|
+
*/
|
|
19
|
+
export const SOURCE_FORMATS = [
|
|
20
|
+
'markdown',
|
|
21
|
+
'text',
|
|
22
|
+
'html',
|
|
23
|
+
'pdf',
|
|
24
|
+
'office',
|
|
25
|
+
'web_page',
|
|
26
|
+
'video',
|
|
27
|
+
'audio',
|
|
28
|
+
'image',
|
|
29
|
+
] as const
|
|
30
|
+
|
|
31
|
+
export type SourceFormat = (typeof SOURCE_FORMATS)[number]
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This version is documents-only (issue 0015 scope cut): web pages and video
|
|
35
|
+
* moved to issues/0017, so only `file` and `text` can be requested.
|
|
36
|
+
*/
|
|
37
|
+
export const CONVERT_INPUT_KINDS = ['file', 'text'] as const
|
|
38
|
+
|
|
39
|
+
export type ConvertInputKind = (typeof CONVERT_INPUT_KINDS)[number]
|
|
40
|
+
|
|
41
|
+
export const TASK_STATES = ['queued', 'running', 'succeeded', 'failed', 'cancelled'] as const
|
|
42
|
+
|
|
43
|
+
export type TaskState = (typeof TASK_STATES)[number]
|
|
44
|
+
|
|
45
|
+
/** Lineage mirrors the knowledge plugin so both ends speak one vocabulary. */
|
|
46
|
+
export interface Lineage {
|
|
47
|
+
sessionId?: string
|
|
48
|
+
turnId?: string
|
|
49
|
+
intentId?: string
|
|
50
|
+
taskId?: string
|
|
51
|
+
subtaskId?: string
|
|
52
|
+
parentSubtaskId?: string
|
|
53
|
+
runId?: string
|
|
54
|
+
traceId?: string
|
|
55
|
+
domain?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type SourceTypeSource = 'agent' | 'user' | 'default'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Front matter written into `index.md`.
|
|
62
|
+
*
|
|
63
|
+
* Required by the knowledge-ready contract: `title`, `created_at`,
|
|
64
|
+
* `updated_at`. `source_id` and `source_type` are optional here because the
|
|
65
|
+
* kernel must not invent them — ingestion fills them in and re-writes the file.
|
|
66
|
+
*/
|
|
67
|
+
export interface ConversionFrontMatter {
|
|
68
|
+
source_id?: string
|
|
69
|
+
source_type?: string
|
|
70
|
+
title: string
|
|
71
|
+
created_at: string
|
|
72
|
+
updated_at: string
|
|
73
|
+
external_id?: string
|
|
74
|
+
source_format?: SourceFormat
|
|
75
|
+
content_hash?: string
|
|
76
|
+
parser?: string
|
|
77
|
+
source_url?: string
|
|
78
|
+
source_path?: string
|
|
79
|
+
source_type_source?: SourceTypeSource
|
|
80
|
+
source_type_candidates?: string[]
|
|
81
|
+
word_count?: number
|
|
82
|
+
asset_count?: number
|
|
83
|
+
lineage?: Lineage
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ConvertAsset {
|
|
87
|
+
/** Path relative to the delivery directory, e.g. `assets/img-1a2b3c4d.png`. */
|
|
88
|
+
rel: string
|
|
89
|
+
/** Absolute path on disk inside the delivery directory. */
|
|
90
|
+
path: string
|
|
91
|
+
bytes: number
|
|
92
|
+
contentType?: string
|
|
93
|
+
sha1: string
|
|
94
|
+
/** Original remote URL when the asset was downloaded. */
|
|
95
|
+
sourceUrl?: string
|
|
96
|
+
/** `inline` when it came from a data: URI, `remote` when downloaded, `local` when copied. */
|
|
97
|
+
origin: 'remote' | 'inline' | 'local'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ConvertWarning {
|
|
101
|
+
code: string
|
|
102
|
+
message: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Everything an ingestion side needs to file this bundle, without guessing. */
|
|
106
|
+
export interface IngestPayload {
|
|
107
|
+
/** Markdown body with a leading front matter block, ready for `add_source`. */
|
|
108
|
+
markdown: string
|
|
109
|
+
/** Same body without front matter. */
|
|
110
|
+
content: string
|
|
111
|
+
title: string
|
|
112
|
+
externalId: string
|
|
113
|
+
contentHash: string
|
|
114
|
+
sourceFormat: SourceFormat
|
|
115
|
+
parser: string
|
|
116
|
+
sourceUrl?: string
|
|
117
|
+
sourcePath?: string
|
|
118
|
+
sourceTypeCandidates: string[]
|
|
119
|
+
/** True when nobody supplied a `source_type` yet. */
|
|
120
|
+
sourceTypeRequired: boolean
|
|
121
|
+
assets: ConvertAsset[]
|
|
122
|
+
/**
|
|
123
|
+
* Image references are relative to the delivery `index.md`. A wiki card lives
|
|
124
|
+
* in `02_wiki/<type>/`, so ingestion must re-base them before a card can feed
|
|
125
|
+
* the multimodal embedder.
|
|
126
|
+
*/
|
|
127
|
+
assetRefBase: 'delivery'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ConvertManifest {
|
|
131
|
+
version: 1
|
|
132
|
+
taskId: string
|
|
133
|
+
inputKind: ConvertInputKind
|
|
134
|
+
sourceFormat: SourceFormat
|
|
135
|
+
parser: string
|
|
136
|
+
title: string
|
|
137
|
+
createdAt: string
|
|
138
|
+
updatedAt: string
|
|
139
|
+
deliveryDir: string
|
|
140
|
+
contentHash: string
|
|
141
|
+
externalId: string
|
|
142
|
+
wordCount: number
|
|
143
|
+
assets: ConvertAsset[]
|
|
144
|
+
warnings: ConvertWarning[]
|
|
145
|
+
/** Version of the external binary / service that did the work, when any. */
|
|
146
|
+
dependencyVersions: Record<string, string>
|
|
147
|
+
lineage?: Lineage
|
|
148
|
+
ingest: IngestPayload
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ConvertTask {
|
|
152
|
+
id: string
|
|
153
|
+
state: TaskState
|
|
154
|
+
inputKind: ConvertInputKind
|
|
155
|
+
/** Human label shown in the console. */
|
|
156
|
+
label: string
|
|
157
|
+
sourceFormat: SourceFormat
|
|
158
|
+
createdAt: string
|
|
159
|
+
updatedAt: string
|
|
160
|
+
startedAt?: string
|
|
161
|
+
finishedAt?: string
|
|
162
|
+
/** 0..1, or null when the adapter cannot report a ratio. */
|
|
163
|
+
progress: number | null
|
|
164
|
+
stage: string
|
|
165
|
+
message?: string
|
|
166
|
+
error?: { code: string; message: string; suggestion?: string }
|
|
167
|
+
warnings: ConvertWarning[]
|
|
168
|
+
deliveryDir?: string
|
|
169
|
+
manifest?: ConvertManifest
|
|
170
|
+
attempts: number
|
|
171
|
+
/** Absolute input path inside the kernel work dir, when the input was a file. */
|
|
172
|
+
inputPath?: string
|
|
173
|
+
request: ConvertRequest
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ConvertRequest {
|
|
177
|
+
kind: ConvertInputKind
|
|
178
|
+
/** Absolute path on the host for kind=file|video; the kernel reads and stages it. */
|
|
179
|
+
localPath?: string
|
|
180
|
+
/** File name inside the kernel work dir (kind=file|video). */
|
|
181
|
+
filename?: string
|
|
182
|
+
/** Inline text (kind=text). */
|
|
183
|
+
text?: string
|
|
184
|
+
title?: string
|
|
185
|
+
/** Caller-supplied business classification; the kernel never invents one. */
|
|
186
|
+
sourceType?: string
|
|
187
|
+
sourceTypeCandidates?: string[]
|
|
188
|
+
sourceTypeSource?: SourceTypeSource
|
|
189
|
+
/** Stable identity for idempotency, e.g. a canonical URL or a file hash. */
|
|
190
|
+
externalId?: string
|
|
191
|
+
lineage?: Lineage
|
|
192
|
+
/** Adapter override, mostly for tests and for the high-quality tier. */
|
|
193
|
+
adapter?: string
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ConvertError {
|
|
197
|
+
code: string
|
|
198
|
+
message: string
|
|
199
|
+
suggestion?: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface ConvertResult {
|
|
203
|
+
task: ConvertTask
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function isSourceFormat(value: unknown): value is SourceFormat {
|
|
207
|
+
return typeof value === 'string' && (SOURCE_FORMATS as readonly string[]).includes(value)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Default business-classification hints per carrier format. */
|
|
211
|
+
export function defaultSourceTypeCandidates(format: SourceFormat): string[] {
|
|
212
|
+
switch (format) {
|
|
213
|
+
case 'pdf':
|
|
214
|
+
return ['article', 'book', 'prd', 'document']
|
|
215
|
+
case 'office':
|
|
216
|
+
return ['prd', 'document']
|
|
217
|
+
case 'markdown':
|
|
218
|
+
case 'text':
|
|
219
|
+
return ['document', 'article']
|
|
220
|
+
// iddoc 档(issue 0017):音视频与图片的业务归类与文档不同,别让它们默认落到 "document"。
|
|
221
|
+
case 'video':
|
|
222
|
+
return ['video', 'course', 'interview', 'document']
|
|
223
|
+
case 'audio':
|
|
224
|
+
return ['audio', 'interview', 'meeting', 'document']
|
|
225
|
+
case 'image':
|
|
226
|
+
return ['image', 'screenshot', 'diagram', 'document']
|
|
227
|
+
default:
|
|
228
|
+
return ['document']
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/delivery.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery bundle writer.
|
|
3
|
+
*
|
|
4
|
+
* Layout is fixed by the 0013 knowledge-ready contract:
|
|
5
|
+
*
|
|
6
|
+
* convert-out/<taskId>/
|
|
7
|
+
* index.md front matter + body, images referenced as assets/…
|
|
8
|
+
* assets/img-*.ext content-addressed local images
|
|
9
|
+
* manifest.json provenance, parser/version, warnings, ingest payload
|
|
10
|
+
*
|
|
11
|
+
* The kernel never writes into the knowledge store; it only produces this
|
|
12
|
+
* directory plus the `ingest` payload that the DSH layer forwards.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdir } from 'node:fs/promises'
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
import type {
|
|
18
|
+
ConvertAsset,
|
|
19
|
+
ConvertManifest,
|
|
20
|
+
ConvertRequest,
|
|
21
|
+
ConvertWarning,
|
|
22
|
+
IngestPayload,
|
|
23
|
+
SourceFormat,
|
|
24
|
+
SourceTypeSource,
|
|
25
|
+
} from './contract.js'
|
|
26
|
+
import { defaultSourceTypeCandidates } from './contract.js'
|
|
27
|
+
import { sha256, toMarkdownDocument, wordCount, writeTextAtomic } from './markdown.js'
|
|
28
|
+
|
|
29
|
+
export interface DeliveryInput {
|
|
30
|
+
taskId: string
|
|
31
|
+
title: string
|
|
32
|
+
request: ConvertRequest
|
|
33
|
+
/** Markdown body with image references relative to `index.md`. */
|
|
34
|
+
markdown: string
|
|
35
|
+
sourceFormat: SourceFormat
|
|
36
|
+
parser: string
|
|
37
|
+
sourceUrl?: string
|
|
38
|
+
sourcePath?: string
|
|
39
|
+
assets: ConvertAsset[]
|
|
40
|
+
warnings: ConvertWarning[]
|
|
41
|
+
dependencyVersions: Record<string, string>
|
|
42
|
+
externalId: string
|
|
43
|
+
createdAt: string
|
|
44
|
+
updatedAt: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildFrontMatter(input: DeliveryInput, contentHash: string): Record<string, unknown> {
|
|
48
|
+
const candidates = input.request.sourceTypeCandidates?.length
|
|
49
|
+
? input.request.sourceTypeCandidates
|
|
50
|
+
: defaultSourceTypeCandidates(input.sourceFormat)
|
|
51
|
+
const frontMatter: Record<string, unknown> = {
|
|
52
|
+
title: input.title,
|
|
53
|
+
created_at: input.createdAt,
|
|
54
|
+
updated_at: input.updatedAt,
|
|
55
|
+
external_id: input.externalId,
|
|
56
|
+
source_format: input.sourceFormat,
|
|
57
|
+
content_hash: contentHash,
|
|
58
|
+
parser: input.parser,
|
|
59
|
+
source_type_candidates: candidates,
|
|
60
|
+
word_count: wordCount(input.markdown),
|
|
61
|
+
asset_count: input.assets.length,
|
|
62
|
+
}
|
|
63
|
+
if (input.request.sourceType) frontMatter.source_type = input.request.sourceType
|
|
64
|
+
if (input.sourceUrl) frontMatter.source_url = input.sourceUrl
|
|
65
|
+
if (input.sourcePath) frontMatter.source_path = input.sourcePath
|
|
66
|
+
if (input.request.sourceTypeSource) frontMatter.source_type_source = input.request.sourceTypeSource
|
|
67
|
+
if (input.request.lineage && Object.keys(input.request.lineage).length > 0) frontMatter.lineage = input.request.lineage
|
|
68
|
+
return frontMatter
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildIngestPayload(input: DeliveryInput, contentHash: string): IngestPayload {
|
|
72
|
+
const candidates = input.request.sourceTypeCandidates?.length
|
|
73
|
+
? input.request.sourceTypeCandidates
|
|
74
|
+
: defaultSourceTypeCandidates(input.sourceFormat)
|
|
75
|
+
return {
|
|
76
|
+
markdown: toMarkdownDocument(buildFrontMatter(input, contentHash), input.markdown),
|
|
77
|
+
content: input.markdown,
|
|
78
|
+
title: input.title,
|
|
79
|
+
externalId: input.externalId,
|
|
80
|
+
contentHash,
|
|
81
|
+
sourceFormat: input.sourceFormat,
|
|
82
|
+
parser: input.parser,
|
|
83
|
+
...(input.sourceUrl ? { sourceUrl: input.sourceUrl } : {}),
|
|
84
|
+
...(input.sourcePath ? { sourcePath: input.sourcePath } : {}),
|
|
85
|
+
sourceTypeCandidates: candidates,
|
|
86
|
+
sourceTypeRequired: !input.request.sourceType,
|
|
87
|
+
assets: input.assets,
|
|
88
|
+
assetRefBase: 'delivery',
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function writeDelivery(deliveryDir: string, input: DeliveryInput): Promise<ConvertManifest> {
|
|
93
|
+
await mkdir(deliveryDir, { recursive: true })
|
|
94
|
+
const contentHash = sha256(input.markdown)
|
|
95
|
+
const frontMatter = buildFrontMatter(input, contentHash)
|
|
96
|
+
const document = toMarkdownDocument(frontMatter, input.markdown)
|
|
97
|
+
await writeTextAtomic(join(deliveryDir, 'index.md'), document)
|
|
98
|
+
|
|
99
|
+
const manifest: ConvertManifest = {
|
|
100
|
+
version: 1,
|
|
101
|
+
taskId: input.taskId,
|
|
102
|
+
inputKind: input.request.kind,
|
|
103
|
+
sourceFormat: input.sourceFormat,
|
|
104
|
+
parser: input.parser,
|
|
105
|
+
title: input.title,
|
|
106
|
+
createdAt: input.createdAt,
|
|
107
|
+
updatedAt: input.updatedAt,
|
|
108
|
+
deliveryDir,
|
|
109
|
+
contentHash,
|
|
110
|
+
externalId: input.externalId,
|
|
111
|
+
wordCount: wordCount(input.markdown),
|
|
112
|
+
assets: input.assets,
|
|
113
|
+
warnings: input.warnings,
|
|
114
|
+
dependencyVersions: input.dependencyVersions,
|
|
115
|
+
...(input.request.lineage ? { lineage: input.request.lineage } : {}),
|
|
116
|
+
ingest: buildIngestPayload(input, contentHash),
|
|
117
|
+
}
|
|
118
|
+
await writeTextAtomic(join(deliveryDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`)
|
|
119
|
+
return manifest
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function sourceTypeSourceOf(value: string | undefined): SourceTypeSource | undefined {
|
|
123
|
+
return value === 'agent' || value === 'user' || value === 'default' ? value : undefined
|
|
124
|
+
}
|