veryfront 0.1.216 → 0.1.217

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 (160) hide show
  1. package/esm/cli/commands/generate/integration-generator-helpers.d.ts +35 -0
  2. package/esm/cli/commands/generate/integration-generator-helpers.d.ts.map +1 -0
  3. package/esm/cli/commands/generate/integration-generator-helpers.js +123 -0
  4. package/esm/cli/commands/generate/integration-generator.d.ts +1 -2
  5. package/esm/cli/commands/generate/integration-generator.d.ts.map +1 -1
  6. package/esm/cli/commands/generate/integration-generator.js +1 -123
  7. package/esm/cli/commands/knowledge/command-helpers.d.ts +48 -0
  8. package/esm/cli/commands/knowledge/command-helpers.d.ts.map +1 -0
  9. package/esm/cli/commands/knowledge/command-helpers.js +216 -0
  10. package/esm/cli/commands/knowledge/command.d.ts +11 -16
  11. package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
  12. package/esm/cli/commands/knowledge/command.js +25 -212
  13. package/esm/cli/mcp/remote-file-tool-helpers.d.ts +5 -0
  14. package/esm/cli/mcp/remote-file-tool-helpers.d.ts.map +1 -0
  15. package/esm/cli/mcp/remote-file-tool-helpers.js +22 -0
  16. package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
  17. package/esm/cli/mcp/remote-file-tools.js +1 -22
  18. package/esm/cli/templates/integration-loader-helpers.d.ts +8 -0
  19. package/esm/cli/templates/integration-loader-helpers.d.ts.map +1 -0
  20. package/esm/cli/templates/integration-loader-helpers.js +29 -0
  21. package/esm/cli/templates/integration-loader.d.ts.map +1 -1
  22. package/esm/cli/templates/integration-loader.js +5 -21
  23. package/esm/cli/templates/manifest.d.ts +1 -0
  24. package/esm/cli/templates/manifest.js +2 -1
  25. package/esm/deno.js +2 -2
  26. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  27. package/esm/src/agent/runtime/index.js +48 -90
  28. package/esm/src/chat/ag-ui-helpers.d.ts +17 -0
  29. package/esm/src/chat/ag-ui-helpers.d.ts.map +1 -0
  30. package/esm/src/chat/ag-ui-helpers.js +111 -0
  31. package/esm/src/chat/ag-ui.d.ts.map +1 -1
  32. package/esm/src/chat/ag-ui.js +1 -111
  33. package/esm/src/config/schemas/config.schema.d.ts +1 -0
  34. package/esm/src/config/schemas/config.schema.d.ts.map +1 -1
  35. package/esm/src/config/schemas/config.schema.js +11 -0
  36. package/esm/src/config/schemas/index.d.ts +11 -1
  37. package/esm/src/config/schemas/index.d.ts.map +1 -1
  38. package/esm/src/errors/error-registry-helpers.d.ts +6 -0
  39. package/esm/src/errors/error-registry-helpers.d.ts.map +1 -0
  40. package/esm/src/errors/error-registry-helpers.js +9 -0
  41. package/esm/src/errors/error-registry.d.ts.map +1 -1
  42. package/esm/src/errors/error-registry.js +4 -3
  43. package/esm/src/extensions/factory-loader.d.ts +29 -0
  44. package/esm/src/extensions/factory-loader.d.ts.map +1 -0
  45. package/esm/src/extensions/factory-loader.js +63 -0
  46. package/esm/src/extensions/index.d.ts +16 -0
  47. package/esm/src/extensions/index.d.ts.map +1 -1
  48. package/esm/src/extensions/index.js +16 -0
  49. package/esm/src/extensions/orchestrate.d.ts +54 -0
  50. package/esm/src/extensions/orchestrate.d.ts.map +1 -0
  51. package/esm/src/extensions/orchestrate.js +116 -0
  52. package/esm/src/html/styles-builder/plugin-loader.d.ts.map +1 -1
  53. package/esm/src/html/styles-builder/plugin-loader.js +23 -0
  54. package/esm/src/html/styles-builder/tailwind-plugin-allowlist.d.ts +29 -0
  55. package/esm/src/html/styles-builder/tailwind-plugin-allowlist.d.ts.map +1 -0
  56. package/esm/src/html/styles-builder/tailwind-plugin-allowlist.js +48 -0
  57. package/esm/src/modules/react-loader/ssr-module-loader/loader-helpers.d.ts +15 -0
  58. package/esm/src/modules/react-loader/ssr-module-loader/loader-helpers.d.ts.map +1 -0
  59. package/esm/src/modules/react-loader/ssr-module-loader/loader-helpers.js +22 -0
  60. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts +0 -2
  61. package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
  62. package/esm/src/modules/react-loader/ssr-module-loader/loader.js +4 -24
  63. package/esm/src/platform/adapters/fs/veryfront/adapter-helpers.d.ts +19 -0
  64. package/esm/src/platform/adapters/fs/veryfront/adapter-helpers.d.ts.map +1 -0
  65. package/esm/src/platform/adapters/fs/veryfront/adapter-helpers.js +26 -0
  66. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
  67. package/esm/src/platform/adapters/fs/veryfront/adapter.js +4 -20
  68. package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.d.ts +1 -0
  69. package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.d.ts.map +1 -1
  70. package/esm/src/platform/adapters/fs/veryfront/read-operations-helpers.js +3 -0
  71. package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts.map +1 -1
  72. package/esm/src/platform/adapters/fs/veryfront/read-operations.js +4 -7
  73. package/esm/src/platform/adapters/fs/veryfront/websocket-manager-helpers.d.ts +28 -0
  74. package/esm/src/platform/adapters/fs/veryfront/websocket-manager-helpers.d.ts.map +1 -0
  75. package/esm/src/platform/adapters/fs/veryfront/websocket-manager-helpers.js +41 -0
  76. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +1 -4
  77. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
  78. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +7 -40
  79. package/esm/src/provider/runtime-loader/tool-input-status.d.ts +17 -0
  80. package/esm/src/provider/runtime-loader/tool-input-status.d.ts.map +1 -0
  81. package/esm/src/provider/runtime-loader/tool-input-status.js +155 -0
  82. package/esm/src/provider/runtime-loader.d.ts +2 -3
  83. package/esm/src/provider/runtime-loader.d.ts.map +1 -1
  84. package/esm/src/provider/runtime-loader.js +2 -155
  85. package/esm/src/rendering/orchestrator/pipeline-helpers.d.ts +8 -0
  86. package/esm/src/rendering/orchestrator/pipeline-helpers.d.ts.map +1 -0
  87. package/esm/src/rendering/orchestrator/pipeline-helpers.js +20 -0
  88. package/esm/src/rendering/orchestrator/pipeline.d.ts +0 -3
  89. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  90. package/esm/src/rendering/orchestrator/pipeline.js +4 -22
  91. package/esm/src/routing/api/module-loader/loader-helpers.d.ts +10 -0
  92. package/esm/src/routing/api/module-loader/loader-helpers.d.ts.map +1 -0
  93. package/esm/src/routing/api/module-loader/loader-helpers.js +62 -0
  94. package/esm/src/routing/api/module-loader/loader.d.ts +1 -1
  95. package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -1
  96. package/esm/src/routing/api/module-loader/loader.js +2 -60
  97. package/esm/src/server/bootstrap.d.ts +22 -2
  98. package/esm/src/server/bootstrap.d.ts.map +1 -1
  99. package/esm/src/server/bootstrap.js +67 -5
  100. package/esm/src/server/dev-ui/manifest.d.ts +2 -0
  101. package/esm/src/server/dev-ui/manifest.js +3 -1
  102. package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -1
  103. package/esm/src/server/handlers/request/api/project-discovery.js +14 -8
  104. package/esm/src/server/production-server.js +1 -1
  105. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.d.ts.map +1 -1
  106. package/esm/src/server/services/rsc/endpoints/rsc-bundles.generated.js +2 -2
  107. package/esm/src/studio/bridge/bridge-bundle.generated.d.ts.map +1 -1
  108. package/esm/src/studio/bridge/bridge-bundle.generated.js +1 -1
  109. package/esm/src/tool/index.d.ts +1 -1
  110. package/esm/src/tool/index.d.ts.map +1 -1
  111. package/esm/src/tool/types.d.ts +20 -0
  112. package/esm/src/tool/types.d.ts.map +1 -1
  113. package/esm/src/utils/version-constant.d.ts +1 -1
  114. package/esm/src/utils/version-constant.js +1 -1
  115. package/package.json +1 -1
  116. package/src/cli/commands/generate/integration-generator-helpers.ts +185 -0
  117. package/src/cli/commands/generate/integration-generator.ts +12 -168
  118. package/src/cli/commands/knowledge/command-helpers.ts +295 -0
  119. package/src/cli/commands/knowledge/command.ts +33 -259
  120. package/src/cli/mcp/remote-file-tool-helpers.ts +27 -0
  121. package/src/cli/mcp/remote-file-tools.ts +6 -28
  122. package/src/cli/templates/integration-loader-helpers.ts +49 -0
  123. package/src/cli/templates/integration-loader.ts +10 -28
  124. package/src/cli/templates/manifest.js +2 -1
  125. package/src/deno.js +2 -2
  126. package/src/src/agent/runtime/index.ts +77 -94
  127. package/src/src/chat/ag-ui-helpers.ts +139 -0
  128. package/src/src/chat/ag-ui.ts +11 -139
  129. package/src/src/config/schemas/config.schema.ts +11 -0
  130. package/src/src/config/schemas/index.ts +15 -1
  131. package/src/src/errors/error-registry-helpers.ts +23 -0
  132. package/src/src/errors/error-registry.ts +8 -3
  133. package/src/src/extensions/factory-loader.ts +76 -0
  134. package/src/src/extensions/index.ts +20 -0
  135. package/src/src/extensions/orchestrate.ts +184 -0
  136. package/src/src/html/styles-builder/plugin-loader.ts +32 -0
  137. package/src/src/html/styles-builder/tailwind-plugin-allowlist.ts +51 -0
  138. package/src/src/modules/react-loader/ssr-module-loader/loader-helpers.ts +37 -0
  139. package/src/src/modules/react-loader/ssr-module-loader/loader.ts +8 -39
  140. package/src/src/platform/adapters/fs/veryfront/adapter-helpers.ts +43 -0
  141. package/src/src/platform/adapters/fs/veryfront/adapter.ts +8 -22
  142. package/src/src/platform/adapters/fs/veryfront/read-operations-helpers.ts +4 -0
  143. package/src/src/platform/adapters/fs/veryfront/read-operations.ts +4 -7
  144. package/src/src/platform/adapters/fs/veryfront/websocket-manager-helpers.ts +73 -0
  145. package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +29 -44
  146. package/src/src/provider/runtime-loader/tool-input-status.ts +210 -0
  147. package/src/src/provider/runtime-loader.ts +6 -203
  148. package/src/src/rendering/orchestrator/pipeline-helpers.ts +35 -0
  149. package/src/src/rendering/orchestrator/pipeline.ts +8 -35
  150. package/src/src/routing/api/module-loader/loader-helpers.ts +68 -0
  151. package/src/src/routing/api/module-loader/loader.ts +8 -65
  152. package/src/src/server/bootstrap.ts +88 -7
  153. package/src/src/server/dev-ui/manifest.js +3 -1
  154. package/src/src/server/handlers/request/api/project-discovery.ts +19 -8
  155. package/src/src/server/production-server.ts +1 -1
  156. package/src/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +2 -2
  157. package/src/src/studio/bridge/bridge-bundle.generated.ts +1 -1
  158. package/src/src/tool/index.ts +1 -0
  159. package/src/src/tool/types.ts +21 -0
  160. package/src/src/utils/version-constant.ts +1 -1
@@ -12,6 +12,18 @@ import { createFileSystem, type FileSystem } from "../../../src/platform/index.j
12
12
  import { ensureDir } from "../../utils/fs.js";
13
13
  import { isInteractive as checkIsInteractive, promptSync } from "../../../src/platform/index.js";
14
14
  import { isCiEnv, isDenoTestingEnv } from "../../../src/config/index.js";
15
+ import {
16
+ getNonInteractiveConfig,
17
+ getToolExecuteBody,
18
+ getToolInputSchema,
19
+ type IntegrationConfig,
20
+ normalizeTokenAuthMethod,
21
+ parseAdditionalAuthParams,
22
+ parseBooleanOption,
23
+ parseScopes,
24
+ type TokenAuthMethod,
25
+ validateIntegrationName,
26
+ } from "./integration-generator-helpers.js";
15
27
  import { select } from "../../utils/terminal-select.js";
16
28
 
17
29
  let fs: FileSystem;
@@ -41,27 +53,6 @@ export interface IntegrationGeneratorOptions {
41
53
  skipPrompts?: boolean;
42
54
  }
43
55
 
44
- type TokenAuthMethod =
45
- | "basic"
46
- | "body"
47
- | "client_secret_basic"
48
- | "client_secret_post"
49
- | "request_body";
50
-
51
- interface IntegrationConfig {
52
- name: string;
53
- displayName: string;
54
- authType: "oauth2" | "api-key";
55
- apiBaseUrl: string;
56
- authorizationUrl?: string;
57
- tokenUrl?: string;
58
- scopes: string[];
59
- tokenAuthMethod: TokenAuthMethod;
60
- additionalAuthParams: Record<string, string>;
61
- usePKCE: boolean;
62
- envVarPrefix: string;
63
- }
64
-
65
56
  function canRunPrompts(): boolean {
66
57
  return !(isCiEnv() || isDenoTestingEnv()) && checkIsInteractive();
67
58
  }
@@ -73,86 +64,6 @@ function promptText(question: string, defaultValue?: string): Promise<string> {
73
64
  return Promise.resolve(input?.trim() || defaultValue || "");
74
65
  }
75
66
 
76
- function parseScopes(scopes?: string): string[] {
77
- return scopes?.split(",").map((s) => s.trim()) || [];
78
- }
79
-
80
- function parseAdditionalAuthParams(params?: string): Record<string, string> {
81
- if (!params?.trim()) return {};
82
-
83
- return Object.fromEntries(
84
- params.split(",")
85
- .map((entry) => entry.trim())
86
- .filter(Boolean)
87
- .map((entry) => {
88
- const separatorIndex = entry.indexOf("=");
89
- if (separatorIndex === -1) {
90
- throw new Error(
91
- "Additional auth params must use key=value pairs separated by commas",
92
- );
93
- }
94
-
95
- const key = entry.slice(0, separatorIndex).trim();
96
- const value = entry.slice(separatorIndex + 1).trim();
97
-
98
- if (!key || !value) {
99
- throw new Error(
100
- "Additional auth params must use non-empty key=value pairs",
101
- );
102
- }
103
-
104
- return [key, value];
105
- }),
106
- );
107
- }
108
-
109
- function normalizeTokenAuthMethod(method?: string): TokenAuthMethod {
110
- switch (method?.trim().toLowerCase()) {
111
- case "basic":
112
- return "basic";
113
- case "body":
114
- return "body";
115
- case "client_secret_basic":
116
- return "client_secret_basic";
117
- case "client_secret_post":
118
- return "client_secret_post";
119
- case "request_body":
120
- case undefined:
121
- case "":
122
- return "request_body";
123
- default:
124
- throw new Error(
125
- "OAuth token auth method must be one of: request_body, body, basic, client_secret_basic, client_secret_post",
126
- );
127
- }
128
- }
129
-
130
- function parseBooleanOption(value: string | boolean | undefined, defaultValue = false): boolean {
131
- if (typeof value === "boolean") return value;
132
- if (!value?.trim()) return defaultValue;
133
-
134
- switch (value.trim().toLowerCase()) {
135
- case "y":
136
- case "yes":
137
- case "true":
138
- case "1":
139
- return true;
140
- case "n":
141
- case "no":
142
- case "false":
143
- case "0":
144
- return false;
145
- default:
146
- throw new Error("Boolean option must be yes/no, true/false, or 1/0");
147
- }
148
- }
149
-
150
- function validateIntegrationName(name: string): void {
151
- if (!name || !/^[a-z][a-z0-9-]*$/.test(name)) {
152
- throw new Error("Integration name must be lowercase letters, numbers, and hyphens");
153
- }
154
- }
155
-
156
67
  export async function generateIntegration(
157
68
  projectDir: string,
158
69
  options: IntegrationGeneratorOptions = {},
@@ -182,32 +93,6 @@ export async function generateIntegration(
182
93
  console.log("");
183
94
  }
184
95
 
185
- function getNonInteractiveConfig(options: IntegrationGeneratorOptions): IntegrationConfig {
186
- const { name, displayName, authType } = options;
187
-
188
- if (!name || !displayName || !authType) {
189
- throw new Error(
190
- "Non-interactive mode requires --name, --display-name, and --auth-type options",
191
- );
192
- }
193
-
194
- const normalizedName = name.toLowerCase();
195
-
196
- return {
197
- name: normalizedName,
198
- displayName,
199
- authType,
200
- apiBaseUrl: options.apiBaseUrl ?? `https://api.${name}.com`,
201
- authorizationUrl: options.authorizationUrl,
202
- tokenUrl: options.tokenUrl,
203
- scopes: parseScopes(options.scopes),
204
- tokenAuthMethod: normalizeTokenAuthMethod(options.tokenAuthMethod),
205
- additionalAuthParams: parseAdditionalAuthParams(options.additionalAuthParams),
206
- usePKCE: parseBooleanOption(options.usePKCE, false),
207
- envVarPrefix: name.toUpperCase().replace(/-/g, "_"),
208
- };
209
- }
210
-
211
96
  async function getInteractiveConfig(
212
97
  options: IntegrationGeneratorOptions,
213
98
  ): Promise<IntegrationConfig> {
@@ -809,47 +694,6 @@ export async function searchItems(query: string): Promise<unknown[]> {
809
694
  cliLogger.debug("Created API client");
810
695
  }
811
696
 
812
- function getToolInputSchema(toolFile: string): string {
813
- return TOOL_FILE_CONTENTS[toolFile]?.inputSchema ?? "";
814
- }
815
-
816
- function getToolExecuteBody(toolFile: string): string {
817
- return TOOL_FILE_CONTENTS[toolFile]?.executeBody ?? "";
818
- }
819
-
820
- const TOOL_FILE_CONTENTS: Record<string, { inputSchema: string; executeBody: string }> = {
821
- "list-items.ts": {
822
- inputSchema: `limit: z.number().optional().describe("Maximum number of items to return"),
823
- offset: z.number().optional().describe("Number of items to skip"),`,
824
- executeBody: `const items = await listItems({
825
- limit: input.limit,
826
- offset: input.offset,
827
- });
828
- return {
829
- success: true,
830
- items,
831
- count: items.length,
832
- };`,
833
- },
834
- "get-item.ts": {
835
- inputSchema: `id: z.string().describe("The ID of the item to retrieve"),`,
836
- executeBody: `const item = await getItem(input.id);
837
- return {
838
- success: true,
839
- item,
840
- };`,
841
- },
842
- "search.ts": {
843
- inputSchema: `query: z.string().describe("Search query"),`,
844
- executeBody: `const results = await searchItems(input.query);
845
- return {
846
- success: true,
847
- results,
848
- count: results.length,
849
- };`,
850
- },
851
- };
852
-
853
697
  async function createToolSkeletons(baseDir: string, config: IntegrationConfig): Promise<void> {
854
698
  const tools = [
855
699
  {
@@ -0,0 +1,295 @@
1
+ import { createFileSystem } from "../../../src/platform/index.js";
2
+ import { basename, extname, join, normalize, relative } from "../../../src/platform/compat/path/index.js";
3
+ import { classifyKnowledgeDirectoryPath, classifyKnowledgeSourcePath } from "./source-policy.js";
4
+ import type {
5
+ KnowledgeIngestFailedFileResult,
6
+ KnowledgeIngestFileResult,
7
+ KnowledgeIngestSkippedFileResult,
8
+ } from "./result.js";
9
+
10
+ export type KnowledgeSource =
11
+ | { kind: "local"; input: string; localPath: string }
12
+ | { kind: "upload"; input: string; uploadPath: string; localPath: string };
13
+
14
+ export interface KnowledgeSourceCollection {
15
+ sources: KnowledgeSource[];
16
+ skipped: KnowledgeIngestSkippedFileResult[];
17
+ }
18
+
19
+ function slugify(value: string): string {
20
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "document";
21
+ }
22
+
23
+ const CHAT_UPLOAD_PREFIX_RE =
24
+ /^chat-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-\d+-[a-z0-9]+-/i;
25
+
26
+ export function normalizeKnowledgeInputPath(inputPath: string): string {
27
+ const normalizedPath = normalize(inputPath).replace(/^\/+/, "").replace(/\\/g, "/");
28
+ if (!normalizedPath || normalizedPath.startsWith("..") || normalizedPath.startsWith("/")) {
29
+ throw new Error(`Invalid knowledge input path: ${inputPath}`);
30
+ }
31
+ return normalizedPath;
32
+ }
33
+
34
+ export function normalizeProjectUploadPath(inputPath: string): string {
35
+ return normalizeKnowledgeInputPath(inputPath);
36
+ }
37
+
38
+ export function formatKnowledgeUploadSource(uploadPath: string): string {
39
+ const normalizedPath = normalizeKnowledgeInputPath(uploadPath);
40
+ return normalizedPath === "uploads" || normalizedPath.startsWith("uploads/")
41
+ ? normalizedPath
42
+ : `uploads/${normalizedPath}`;
43
+ }
44
+
45
+ export function resolveExplicitUploadPath(inputPath: string): string {
46
+ const normalizedInput = normalizeKnowledgeInputPath(inputPath);
47
+ const displayInput = inputPath.replace(/\\/g, "/");
48
+ const uploadPath = normalizeProjectUploadPath(inputPath);
49
+ if (
50
+ !uploadPath || uploadPath === "uploads" || normalizedInput === "uploads" ||
51
+ normalizedInput.endsWith("/")
52
+ ) {
53
+ throw new Error(
54
+ `Directory upload references require --path <prefix> --all: ${displayInput}`,
55
+ );
56
+ }
57
+ return uploadPath;
58
+ }
59
+
60
+ export function isLikelyLocalPath(value: string): boolean {
61
+ return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") ||
62
+ /^[A-Za-z]:[\\/]/.test(value);
63
+ }
64
+
65
+ export function isProjectUploadReference(value: string): boolean {
66
+ if (isLikelyLocalPath(value)) return false;
67
+ const normalizedValue = normalize(value).replace(/\\/g, "/").replace(/^\/+/, "");
68
+ return normalizedValue === "uploads" || normalizedValue.startsWith("uploads/");
69
+ }
70
+
71
+ export function stripChatUploadPrefix(name: string): string {
72
+ return name.replace(CHAT_UPLOAD_PREFIX_RE, "");
73
+ }
74
+
75
+ export function resolveKnowledgeDownloadOutputDir(outputDir: string): string {
76
+ return join(outputDir, ".uploads");
77
+ }
78
+
79
+ function createSkippedKnowledgeSource(input: {
80
+ source: string;
81
+ localSourcePath?: string | null;
82
+ message: string;
83
+ reason: KnowledgeIngestSkippedFileResult["reason"];
84
+ }): KnowledgeIngestSkippedFileResult {
85
+ return {
86
+ source: input.source,
87
+ localSourcePath: input.localSourcePath ?? null,
88
+ message: input.message,
89
+ reason: input.reason,
90
+ };
91
+ }
92
+
93
+ export function createFailedKnowledgeSource(input: {
94
+ source: string;
95
+ localSourcePath: string;
96
+ message: string;
97
+ reason: KnowledgeIngestFailedFileResult["reason"];
98
+ }): KnowledgeIngestFailedFileResult {
99
+ return {
100
+ source: input.source,
101
+ localSourcePath: input.localSourcePath,
102
+ message: input.message,
103
+ reason: input.reason,
104
+ };
105
+ }
106
+
107
+ function classifySourceOrSkip(input: {
108
+ source: string;
109
+ localSourcePath?: string | null;
110
+ }): KnowledgeIngestSkippedFileResult | null {
111
+ const decision = classifyKnowledgeSourcePath(input.source);
112
+ if (decision.kind === "ingest") {
113
+ return null;
114
+ }
115
+
116
+ return createSkippedKnowledgeSource({
117
+ source: input.source,
118
+ localSourcePath: input.localSourcePath,
119
+ message: decision.message,
120
+ reason: decision.reason,
121
+ });
122
+ }
123
+
124
+ function classifyDirectoryOrSkip(
125
+ input: { source: string },
126
+ ): KnowledgeIngestSkippedFileResult | null {
127
+ const decision = classifyKnowledgeDirectoryPath(input.source);
128
+ if (decision.kind === "ingest") {
129
+ return null;
130
+ }
131
+
132
+ return createSkippedKnowledgeSource({
133
+ source: input.source,
134
+ localSourcePath: null,
135
+ message: decision.message,
136
+ reason: decision.reason,
137
+ });
138
+ }
139
+
140
+ export async function collectLocalFiles(
141
+ root: string,
142
+ recursive: boolean,
143
+ ): Promise<KnowledgeSourceCollection> {
144
+ const fs = createFileSystem();
145
+ const stat = await fs.stat(root);
146
+ if (stat.isFile) {
147
+ const skipped = classifySourceOrSkip({ source: root, localSourcePath: root });
148
+ return skipped == null
149
+ ? {
150
+ sources: [{ kind: "local", input: root, localPath: root }],
151
+ skipped: [],
152
+ }
153
+ : {
154
+ sources: [],
155
+ skipped: [skipped],
156
+ };
157
+ }
158
+ if (!stat.isDirectory) {
159
+ return { sources: [], skipped: [] };
160
+ }
161
+
162
+ const skippedRootDirectory = classifyDirectoryOrSkip({ source: root });
163
+ if (skippedRootDirectory != null) {
164
+ return {
165
+ sources: [],
166
+ skipped: [skippedRootDirectory],
167
+ };
168
+ }
169
+
170
+ const collection: KnowledgeSourceCollection = {
171
+ sources: [],
172
+ skipped: [],
173
+ };
174
+ async function walk(dir: string): Promise<void> {
175
+ for await (const entry of fs.readDir(dir)) {
176
+ const entryPath = join(dir, entry.name);
177
+ if (entry.isDirectory) {
178
+ const skipped = classifyDirectoryOrSkip({ source: entryPath });
179
+ if (skipped != null) {
180
+ collection.skipped.push(skipped);
181
+ continue;
182
+ }
183
+ if (recursive) await walk(entryPath);
184
+ continue;
185
+ }
186
+
187
+ if (!entry.isFile) {
188
+ continue;
189
+ }
190
+
191
+ const skipped = classifySourceOrSkip({ source: entryPath, localSourcePath: entryPath });
192
+ if (skipped != null) {
193
+ collection.skipped.push(skipped);
194
+ continue;
195
+ }
196
+
197
+ collection.sources.push({ kind: "local", input: root, localPath: entryPath });
198
+ }
199
+ }
200
+
201
+ await walk(root);
202
+ collection.sources.sort((left, right) => left.localPath.localeCompare(right.localPath));
203
+ collection.skipped.sort((left, right) => left.source.localeCompare(right.source));
204
+ return collection;
205
+ }
206
+
207
+ export function buildSourceReference(source: KnowledgeSource): string {
208
+ return source.kind === "upload"
209
+ ? formatKnowledgeUploadSource(source.uploadPath)
210
+ : source.localPath;
211
+ }
212
+
213
+ export function buildSuggestedSlug(source: KnowledgeSource, index: number): string {
214
+ const normalized = normalize(
215
+ source.kind === "upload" ? source.uploadPath : source.localPath,
216
+ ).replace(/\\/g, "/");
217
+
218
+ let stripped: string;
219
+ if (source.kind === "upload") {
220
+ stripped = normalized
221
+ .replace(/^\/workspace\/uploads\//, "")
222
+ .replace(/^\/workspace\//, "")
223
+ .replace(/^uploads\//, "")
224
+ .replace(/\.[^.]+$/, "");
225
+ } else if (normalized.startsWith("/workspace/uploads/")) {
226
+ stripped = normalized.replace(/^\/workspace\/uploads\//, "").replace(/\.[^.]+$/, "");
227
+ } else if (normalized.startsWith("/workspace/")) {
228
+ stripped = normalized.replace(/^\/workspace\//, "").replace(/\.[^.]+$/, "");
229
+ } else if (normalized.startsWith("/")) {
230
+ stripped = basename(normalized, extname(normalized));
231
+ } else {
232
+ stripped = normalized.replace(/\.[^.]+$/, "");
233
+ }
234
+
235
+ const lastSlash = stripped.lastIndexOf("/");
236
+ if (lastSlash >= 0) {
237
+ const dir = stripped.slice(0, lastSlash + 1);
238
+ const file = stripChatUploadPrefix(stripped.slice(lastSlash + 1));
239
+ stripped = file ? `${dir}${file}` : stripped;
240
+ } else {
241
+ stripped = stripChatUploadPrefix(stripped) || stripped;
242
+ }
243
+
244
+ return slugify(stripped || basename(normalized, extname(normalized)) || `document-${index + 1}`);
245
+ }
246
+
247
+ export function ensureUniqueSlugs(sources: KnowledgeSource[]): string[] {
248
+ const counts = new Map<string, number>();
249
+ return sources.map((source, index) => {
250
+ const baseSlug = buildSuggestedSlug(source, index);
251
+ const nextCount = (counts.get(baseSlug) ?? 0) + 1;
252
+ counts.set(baseSlug, nextCount);
253
+ return nextCount === 1 ? baseSlug : `${baseSlug}-${nextCount}`;
254
+ });
255
+ }
256
+
257
+ export function deriveKnowledgeRemotePath(
258
+ outputPath: string,
259
+ outputDir: string,
260
+ knowledgePath: string,
261
+ ): string {
262
+ const relativeOutputPath = relative(outputDir, outputPath).replace(/\\/g, "/");
263
+ if (!relativeOutputPath || relativeOutputPath.startsWith("..")) {
264
+ throw new Error(`Output path is outside output directory: ${outputPath}`);
265
+ }
266
+ const prefix = normalizeKnowledgeInputPath(knowledgePath);
267
+ const normalizedRelative = normalize(relativeOutputPath).replace(/^\/+/, "");
268
+ return `${prefix}/${normalizedRelative}`.replace(/\\/g, "/");
269
+ }
270
+
271
+ export function createKnowledgeIngestResult(input: {
272
+ source: string;
273
+ localSourcePath: string;
274
+ outputPath: string;
275
+ remotePath: string;
276
+ parser: {
277
+ slug: string;
278
+ stats: Record<string, unknown>;
279
+ warnings: string[];
280
+ source_type: string;
281
+ summary: string;
282
+ };
283
+ }): KnowledgeIngestFileResult {
284
+ return {
285
+ source: input.source,
286
+ localSourcePath: input.localSourcePath,
287
+ outputPath: input.outputPath,
288
+ remotePath: input.remotePath,
289
+ slug: input.parser.slug,
290
+ sourceType: input.parser.source_type,
291
+ summary: input.parser.summary,
292
+ stats: input.parser.stats,
293
+ warnings: input.parser.warnings,
294
+ };
295
+ }