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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/media-to-md.mjs +168 -0
  4. package/bin/tita-convert.mjs +85 -0
  5. package/docs/media-to-md.md +143 -0
  6. package/lib/adapters/anydoc.d.ts +73 -0
  7. package/lib/adapters/anydoc.d.ts.map +1 -0
  8. package/lib/adapters/anydoc.js +295 -0
  9. package/lib/adapters/anydoc.js.map +1 -0
  10. package/lib/adapters/iddoc.d.ts +28 -0
  11. package/lib/adapters/iddoc.d.ts.map +1 -0
  12. package/lib/adapters/iddoc.js +153 -0
  13. package/lib/adapters/iddoc.js.map +1 -0
  14. package/lib/adapters/index.d.ts +41 -0
  15. package/lib/adapters/index.d.ts.map +1 -0
  16. package/lib/adapters/index.js +115 -0
  17. package/lib/adapters/index.js.map +1 -0
  18. package/lib/adapters/scanned.d.ts +28 -0
  19. package/lib/adapters/scanned.d.ts.map +1 -0
  20. package/lib/adapters/scanned.js +121 -0
  21. package/lib/adapters/scanned.js.map +1 -0
  22. package/lib/adapters/text.d.ts +29 -0
  23. package/lib/adapters/text.d.ts.map +1 -0
  24. package/lib/adapters/text.js +135 -0
  25. package/lib/adapters/text.js.map +1 -0
  26. package/lib/adapters/types.d.ts +65 -0
  27. package/lib/adapters/types.d.ts.map +1 -0
  28. package/lib/adapters/types.js +11 -0
  29. package/lib/adapters/types.js.map +1 -0
  30. package/lib/contract.d.ts +185 -0
  31. package/lib/contract.d.ts.map +1 -0
  32. package/lib/contract.js +59 -0
  33. package/lib/contract.js.map +1 -0
  34. package/lib/delivery.d.ts +36 -0
  35. package/lib/delivery.d.ts.map +1 -0
  36. package/lib/delivery.js +97 -0
  37. package/lib/delivery.js.map +1 -0
  38. package/lib/http.d.ts +34 -0
  39. package/lib/http.d.ts.map +1 -0
  40. package/lib/http.js +372 -0
  41. package/lib/http.js.map +1 -0
  42. package/lib/markdown.d.ts +32 -0
  43. package/lib/markdown.d.ts.map +1 -0
  44. package/lib/markdown.js +145 -0
  45. package/lib/markdown.js.map +1 -0
  46. package/lib/media/binaries.d.ts +26 -0
  47. package/lib/media/binaries.d.ts.map +1 -0
  48. package/lib/media/binaries.js +60 -0
  49. package/lib/media/binaries.js.map +1 -0
  50. package/lib/media/convert.d.ts +19 -0
  51. package/lib/media/convert.d.ts.map +1 -0
  52. package/lib/media/convert.js +210 -0
  53. package/lib/media/convert.js.map +1 -0
  54. package/lib/media/index.d.ts +27 -0
  55. package/lib/media/index.d.ts.map +1 -0
  56. package/lib/media/index.js +27 -0
  57. package/lib/media/index.js.map +1 -0
  58. package/lib/media/markdown.d.ts +40 -0
  59. package/lib/media/markdown.d.ts.map +1 -0
  60. package/lib/media/markdown.js +89 -0
  61. package/lib/media/markdown.js.map +1 -0
  62. package/lib/media/media.d.ts +45 -0
  63. package/lib/media/media.d.ts.map +1 -0
  64. package/lib/media/media.js +167 -0
  65. package/lib/media/media.js.map +1 -0
  66. package/lib/media/models.d.ts +31 -0
  67. package/lib/media/models.d.ts.map +1 -0
  68. package/lib/media/models.js +87 -0
  69. package/lib/media/models.js.map +1 -0
  70. package/lib/media/scanned.d.ts +18 -0
  71. package/lib/media/scanned.d.ts.map +1 -0
  72. package/lib/media/scanned.js +179 -0
  73. package/lib/media/scanned.js.map +1 -0
  74. package/lib/media/types.d.ts +154 -0
  75. package/lib/media/types.d.ts.map +1 -0
  76. package/lib/media/types.js +23 -0
  77. package/lib/media/types.js.map +1 -0
  78. package/lib/server.d.ts +35 -0
  79. package/lib/server.d.ts.map +1 -0
  80. package/lib/server.js +64 -0
  81. package/lib/server.js.map +1 -0
  82. package/lib/service.d.ts +170 -0
  83. package/lib/service.d.ts.map +1 -0
  84. package/lib/service.js +562 -0
  85. package/lib/service.js.map +1 -0
  86. package/package.json +59 -0
  87. package/scripts/asr.py +55 -0
  88. package/scripts/ocr.py +40 -0
  89. package/scripts/pdf_pages.py +49 -0
  90. package/scripts/vendor.mjs +76 -0
  91. package/src/adapters/anydoc.ts +356 -0
  92. package/src/adapters/iddoc.ts +171 -0
  93. package/src/adapters/index.ts +147 -0
  94. package/src/adapters/scanned.ts +139 -0
  95. package/src/adapters/text.ts +146 -0
  96. package/src/adapters/types.ts +76 -0
  97. package/src/contract.ts +230 -0
  98. package/src/delivery.ts +124 -0
  99. package/src/http.ts +394 -0
  100. package/src/markdown.ts +141 -0
  101. package/src/media/binaries.ts +67 -0
  102. package/src/media/convert.ts +232 -0
  103. package/src/media/index.ts +43 -0
  104. package/src/media/markdown.ts +105 -0
  105. package/src/media/media.ts +204 -0
  106. package/src/media/models.ts +124 -0
  107. package/src/media/scanned.ts +200 -0
  108. package/src/media/types.ts +171 -0
  109. package/src/server.ts +85 -0
  110. package/src/service.ts +639 -0
  111. package/web/app.js +382 -0
  112. package/web/index.html +217 -0
  113. package/web/styles.css +263 -0
  114. package/web/vendor/icons.js +25 -0
  115. package/web/vendor/vue.global.prod.js +14 -0
@@ -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
+ }
@@ -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
+ }