@deployxai/dxc 0.2.5 → 0.3.0

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.
@@ -9,11 +9,9 @@ import {
9
9
  LocalStateError,
10
10
  contentKnowledgeImportResultSchema,
11
11
  contentKnowledgeModelSchema,
12
- contentKnowledgeQuoteInitResultSchema,
13
12
  contentKnowledgeQuoteListResultSchema,
14
13
  contentKnowledgeQuoteMutationResultSchema,
15
14
  contentKnowledgeQuoteStatusResultSchema,
16
- contentKnowledgeQuoteSyncResultSchema,
17
15
  contentKnowledgeRemoveResultSchema,
18
16
  contentKnowledgeSearchModeSchema,
19
17
  contentKnowledgeSearchResultSchema,
@@ -25,7 +23,7 @@ import {
25
23
  resolveUserPath,
26
24
  writePrivateBuffer,
27
25
  writePrivateJson
28
- } from "./chunk-JB5USZ4F.js";
26
+ } from "./chunk-6JNQZHAQ.js";
29
27
 
30
28
  // apps/cli/src/knowledge.ts
31
29
  import { createHash as createHash2, randomUUID } from "node:crypto";
@@ -351,12 +349,14 @@ var MAXIMUM_DIRECTORY_ARTICLES = 1e3;
351
349
  var MAXIMUM_DIRECTORY_DEPTH = 32;
352
350
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
353
351
  var KnowledgeCliError = class extends Error {
354
- constructor(code, message) {
352
+ constructor(code, message, details) {
355
353
  super(message);
356
354
  this.code = code;
355
+ this.details = details;
357
356
  this.name = "KnowledgeCliError";
358
357
  }
359
358
  code;
359
+ details;
360
360
  };
361
361
  var quoteMetadataValueSchema = z.string().trim().min(1).max(300).refine((value) => !/[\r\n]/u.test(value) && value !== "\uFF08\u65E0\uFF09", {
362
362
  message: "quote metadata must stay on one line"
@@ -426,6 +426,39 @@ function skippedArticle(error, inputPath) {
426
426
  function sha2562(value) {
427
427
  return createHash2("sha256").update(value).digest("hex");
428
428
  }
429
+ function knowledgeModelChoices(operation, inputFingerprint) {
430
+ const choiceId = (mode) => {
431
+ const payload = Buffer.from(
432
+ JSON.stringify({ inputFingerprint, mode, operation, version: 1 }),
433
+ "utf8"
434
+ ).toString("base64url");
435
+ return `knowledge-choice1_${payload}.${sha2562(payload).slice(0, 32)}`;
436
+ };
437
+ return {
438
+ choices: [
439
+ { choiceId: choiceId("retry-semantic"), label: "\u91CD\u8BD5\u672C\u5730\u8BED\u4E49\u6A21\u578B" },
440
+ { choiceId: choiceId("lexical"), label: "\u672C\u6B21\u4EC5\u4F7F\u7528\u5173\u952E\u8BCD" }
441
+ ],
442
+ purpose: "knowledge-model-unavailable",
443
+ type: "select-choice"
444
+ };
445
+ }
446
+ function selectedKnowledgeMode(action, choiceId) {
447
+ if (choiceId === void 0) {
448
+ return "default";
449
+ }
450
+ const selected = action.choices.find((choice) => choice.choiceId === choiceId);
451
+ if (selected === void 0) {
452
+ throw new KnowledgeCliError(
453
+ "DXC_KNOWLEDGE_QUERY_INVALID",
454
+ "Knowledge choice does not belong to the current operation"
455
+ );
456
+ }
457
+ return selected.label === "\u672C\u6B21\u4EC5\u4F7F\u7528\u5173\u952E\u8BCD" ? "lexical" : "retry-semantic";
458
+ }
459
+ function knowledgeModelUnavailable(message, action, code) {
460
+ return new KnowledgeCliError(code, message, { nextAction: action });
461
+ }
429
462
  function normalizedMarkdown(value) {
430
463
  return value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").trim();
431
464
  }
@@ -1787,44 +1820,65 @@ var KnowledgeCli = class {
1787
1820
  lexicalOnly
1788
1821
  });
1789
1822
  }
1790
- async initQuotes() {
1823
+ async importSource(source, lexicalOnly, choiceId) {
1824
+ const target = resolveUserPath({
1825
+ currentDirectory: this.#currentDirectory,
1826
+ homeDirectory: this.#homeDirectory,
1827
+ input: source
1828
+ });
1829
+ let metadata;
1830
+ try {
1831
+ metadata = await lstat(target);
1832
+ } catch {
1833
+ throw new KnowledgeCliError(
1834
+ "DXC_KNOWLEDGE_INPUT_NOT_FOUND",
1835
+ "Knowledge import source was not found"
1836
+ );
1837
+ }
1838
+ if (metadata.isSymbolicLink()) {
1839
+ throw new KnowledgeCliError(
1840
+ "DXC_KNOWLEDGE_INPUT_NOT_FOUND",
1841
+ "Knowledge import source must not be a symbolic link"
1842
+ );
1843
+ }
1844
+ const action = knowledgeModelChoices("import", sha2562(target));
1845
+ const selectedMode = selectedKnowledgeMode(action, choiceId);
1846
+ const effectiveLexicalOnly = selectedMode === "lexical" || selectedMode === "default" && lexicalOnly;
1847
+ try {
1848
+ if (metadata.isDirectory()) {
1849
+ return await this.importDirectory(target, effectiveLexicalOnly);
1850
+ }
1851
+ if (metadata.isFile()) {
1852
+ return await this.importArticles({ files: [target], lexicalOnly: effectiveLexicalOnly });
1853
+ }
1854
+ throw new KnowledgeCliError(
1855
+ "DXC_KNOWLEDGE_INPUT_NOT_FOUND",
1856
+ "Knowledge import source must be a file or directory"
1857
+ );
1858
+ } catch (error) {
1859
+ if (error instanceof KnowledgeCliError && error.code === "DXC_KNOWLEDGE_MODEL_FAILED" && !effectiveLexicalOnly) {
1860
+ throw knowledgeModelUnavailable(error.message, action, error.code);
1861
+ }
1862
+ throw error;
1863
+ }
1864
+ }
1865
+ async #initQuotes() {
1791
1866
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1792
1867
  const exists = await quoteSourceExists(sourcePath);
1793
1868
  if (!exists) {
1794
1869
  const legacyPath = legacyQuoteSourcePath(this.#homeDirectory);
1795
1870
  const legacyExists = await quoteSourceExists(legacyPath);
1796
- const quotes = legacyExists ? (await readQuoteSource(legacyPath, true)).quotes : [
1797
- {
1798
- quoteId: this.#randomUUID(),
1799
- status: "active",
1800
- text: "\u628A\u8FD9\u53E5\u66FF\u6362\u6210\u4F60\u81EA\u5DF1\u7684\u91D1\u53E5\uFF1B\u91D1\u53E5 ID \u4E00\u65E6\u521B\u5EFA\u5C31\u4E0D\u8981\u4FEE\u6539\u3002",
1801
- tags: ["\u793A\u4F8B"],
1802
- source: "\u4E2A\u4EBA\u539F\u521B",
1803
- origin: "original",
1804
- attribution: null,
1805
- verbatimUse: "original"
1806
- }
1807
- ];
1871
+ const quotes = legacyExists ? (await readQuoteSource(legacyPath, true)).quotes : [];
1808
1872
  await writeQuoteSource(sourcePath, quotes);
1809
1873
  }
1810
- const database = await openDatabase(this.#databasePath);
1811
- try {
1812
- return contentKnowledgeQuoteInitResultSchema.parse({
1813
- command: "knowledge.quotes.init",
1814
- data: { ...quoteSourceSummary(database, true), created: !exists },
1815
- ok: true
1816
- });
1817
- } finally {
1818
- database.close();
1819
- }
1874
+ await this.#syncQuotes(true);
1820
1875
  }
1821
1876
  async listQuotes() {
1822
1877
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1823
1878
  if (!await quoteSourceExists(sourcePath)) {
1824
- throw new KnowledgeCliError(
1825
- "DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
1826
- "Quote source file is not initialized; run knowledge quotes init first"
1827
- );
1879
+ await this.#initQuotes();
1880
+ } else {
1881
+ await this.#syncQuotes(true);
1828
1882
  }
1829
1883
  const source = await readQuoteSource(sourcePath);
1830
1884
  return contentKnowledgeQuoteListResultSchema.parse({
@@ -1836,12 +1890,20 @@ var KnowledgeCli = class {
1836
1890
  async addQuote(options) {
1837
1891
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1838
1892
  if (!await quoteSourceExists(sourcePath)) {
1839
- throw new KnowledgeCliError(
1840
- "DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
1841
- "Quote source file is not initialized; run knowledge quotes init first"
1842
- );
1893
+ await this.#initQuotes();
1843
1894
  }
1844
1895
  const source = await readQuoteSource(sourcePath);
1896
+ const duplicate = source.quotes.find(
1897
+ (item) => item.status === "active" && item.text.trim() === options.text.trim()
1898
+ );
1899
+ if (duplicate !== void 0) {
1900
+ await this.#syncQuotes(false);
1901
+ return contentKnowledgeQuoteMutationResultSchema.parse({
1902
+ command: "knowledge.quotes.add",
1903
+ data: { quote: quoteRecord(duplicate), synced: true },
1904
+ ok: true
1905
+ });
1906
+ }
1845
1907
  const quote = quoteSourceItemSchema.safeParse({
1846
1908
  attribution: options.attribution ?? null,
1847
1909
  origin: options.origin ?? "original",
@@ -1855,10 +1917,17 @@ var KnowledgeCli = class {
1855
1917
  if (!quote.success) {
1856
1918
  throw new KnowledgeCliError("DXC_KNOWLEDGE_QUOTE_INVALID", "Quote input is invalid");
1857
1919
  }
1858
- await writeQuoteSource(sourcePath, [...source.quotes, quote.data]);
1920
+ const nextQuotes = [...source.quotes, quote.data];
1921
+ await writeQuoteSource(sourcePath, nextQuotes);
1922
+ try {
1923
+ await this.#syncQuotes(false);
1924
+ } catch (error) {
1925
+ await writeQuoteSource(sourcePath, source.quotes);
1926
+ throw error;
1927
+ }
1859
1928
  return contentKnowledgeQuoteMutationResultSchema.parse({
1860
1929
  command: "knowledge.quotes.add",
1861
- data: { pendingSync: true, quote: quoteRecord(quote.data) },
1930
+ data: { quote: quoteRecord(quote.data), synced: true },
1862
1931
  ok: true
1863
1932
  });
1864
1933
  }
@@ -1868,10 +1937,7 @@ var KnowledgeCli = class {
1868
1937
  }
1869
1938
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1870
1939
  if (!await quoteSourceExists(sourcePath)) {
1871
- throw new KnowledgeCliError(
1872
- "DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
1873
- "Quote source file is not initialized; run knowledge quotes init first"
1874
- );
1940
+ await this.#initQuotes();
1875
1941
  }
1876
1942
  const source = await readQuoteSource(sourcePath);
1877
1943
  const index = source.quotes.findIndex((quote) => quote.quoteId === quoteId);
@@ -1899,9 +1965,15 @@ var KnowledgeCli = class {
1899
1965
  const quotes = source.quotes.map(sourceQuote);
1900
1966
  quotes[index] = updated.data;
1901
1967
  await writeQuoteSource(sourcePath, quotes);
1968
+ try {
1969
+ await this.#syncQuotes(false);
1970
+ } catch (error) {
1971
+ await writeQuoteSource(sourcePath, source.quotes);
1972
+ throw error;
1973
+ }
1902
1974
  return contentKnowledgeQuoteMutationResultSchema.parse({
1903
1975
  command: "knowledge.quotes.update",
1904
- data: { pendingSync: true, quote: quoteRecord(updated.data) },
1976
+ data: { quote: quoteRecord(updated.data), synced: true },
1905
1977
  ok: true
1906
1978
  });
1907
1979
  }
@@ -1911,10 +1983,7 @@ var KnowledgeCli = class {
1911
1983
  }
1912
1984
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1913
1985
  if (!await quoteSourceExists(sourcePath)) {
1914
- throw new KnowledgeCliError(
1915
- "DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
1916
- "Quote source file is not initialized; run knowledge quotes init first"
1917
- );
1986
+ await this.#initQuotes();
1918
1987
  }
1919
1988
  const source = await readQuoteSource(sourcePath);
1920
1989
  const index = source.quotes.findIndex((quote) => quote.quoteId === quoteId);
@@ -1932,9 +2001,15 @@ var KnowledgeCli = class {
1932
2001
  const quotes = source.quotes.map(sourceQuote);
1933
2002
  quotes[index] = archived;
1934
2003
  await writeQuoteSource(sourcePath, quotes);
2004
+ try {
2005
+ await this.#syncQuotes(false);
2006
+ } catch (error) {
2007
+ await writeQuoteSource(sourcePath, source.quotes);
2008
+ throw error;
2009
+ }
1935
2010
  return contentKnowledgeQuoteMutationResultSchema.parse({
1936
2011
  command: "knowledge.quotes.archive",
1937
- data: { pendingSync: true, quote: quoteRecord(archived) },
2012
+ data: { quote: quoteRecord(archived), synced: true },
1938
2013
  ok: true
1939
2014
  });
1940
2015
  }
@@ -1964,12 +2039,12 @@ var KnowledgeCli = class {
1964
2039
  database.close();
1965
2040
  }
1966
2041
  }
1967
- async syncQuotes(lexicalOnly) {
2042
+ async #syncQuotes(lexicalOnly) {
1968
2043
  const sourcePath = quoteSourcePath(this.#homeDirectory);
1969
2044
  if (!await quoteSourceExists(sourcePath)) {
1970
2045
  throw new KnowledgeCliError(
1971
2046
  "DXC_KNOWLEDGE_QUOTE_SOURCE_NOT_INITIALIZED",
1972
- "Quote source file is not initialized; run knowledge quotes init first"
2047
+ "Quote source file is not initialized"
1973
2048
  );
1974
2049
  }
1975
2050
  if (!lexicalOnly) {
@@ -1996,7 +2071,7 @@ var KnowledgeCli = class {
1996
2071
  }
1997
2072
  }
1998
2073
  }
1999
- const status = syncQuotes(
2074
+ syncQuotes(
2000
2075
  database,
2001
2076
  source.quotes,
2002
2077
  source.contentSha256,
@@ -2004,15 +2079,6 @@ var KnowledgeCli = class {
2004
2079
  this.#now().toISOString(),
2005
2080
  unchanged
2006
2081
  );
2007
- return contentKnowledgeQuoteSyncResultSchema.parse({
2008
- command: "knowledge.quotes.sync",
2009
- data: {
2010
- ...quoteSourceSummary(database, true),
2011
- model: lexicalOnly ? null : this.#embedder.model,
2012
- status
2013
- },
2014
- ok: true
2015
- });
2016
2082
  } finally {
2017
2083
  database.close();
2018
2084
  }
@@ -2057,9 +2123,16 @@ var KnowledgeCli = class {
2057
2123
  database.close();
2058
2124
  }
2059
2125
  }
2060
- async search(queryInput, modeInput, limitInput) {
2126
+ async search(queryInput, modeInput, limitInput, choiceId) {
2061
2127
  const query = queryInput.trim();
2062
- const mode = contentKnowledgeSearchModeSchema.safeParse(modeInput);
2128
+ const modelAction = knowledgeModelChoices(
2129
+ "search",
2130
+ sha2562(JSON.stringify({ limit: limitInput, mode: modeInput, query }))
2131
+ );
2132
+ const selectedMode = selectedKnowledgeMode(modelAction, choiceId);
2133
+ const mode = contentKnowledgeSearchModeSchema.safeParse(
2134
+ selectedMode === "lexical" ? "lexical" : modeInput
2135
+ );
2063
2136
  if (query.length === 0 || query.length > 500 || !mode.success || !Number.isSafeInteger(limitInput) || limitInput < 1 || limitInput > MAXIMUM_RESULTS) {
2064
2137
  throw new KnowledgeCliError("DXC_KNOWLEDGE_QUERY_INVALID", "Knowledge query is invalid");
2065
2138
  }
@@ -2083,7 +2156,8 @@ var KnowledgeCli = class {
2083
2156
  if (usesSemantic && (chunks.some((chunk) => chunk.embedding === null) || quotes.some((quote) => quote.embedding === null))) {
2084
2157
  throw new KnowledgeCliError(
2085
2158
  "DXC_KNOWLEDGE_SEMANTIC_NOT_READY",
2086
- "All active knowledge items must have a semantic index before hybrid search"
2159
+ "All active knowledge items must have a semantic index before hybrid search",
2160
+ { nextAction: modelAction }
2087
2161
  );
2088
2162
  }
2089
2163
  const lexical = mode.data === "semantic" ? [] : lexicalRanking(database, query);
@@ -2095,9 +2169,10 @@ var KnowledgeCli = class {
2095
2169
  try {
2096
2170
  queryVector = await this.#embedder.embedQuery(query);
2097
2171
  } catch {
2098
- throw new KnowledgeCliError(
2099
- "DXC_KNOWLEDGE_MODEL_FAILED",
2100
- "Local semantic model could not process the query"
2172
+ throw knowledgeModelUnavailable(
2173
+ "Local semantic model could not process the query",
2174
+ modelAction,
2175
+ "DXC_KNOWLEDGE_MODEL_FAILED"
2101
2176
  );
2102
2177
  }
2103
2178
  semantic = semanticRanking(chunks, queryVector);