@workos/oagen 0.29.3 → 0.29.4

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.
@@ -3775,6 +3775,7 @@ function extractKotlinClassMembers(classBody, source) {
3775
3775
  const children = classBody.namedChildren;
3776
3776
  for (let i = 0; i < children.length; i++) {
3777
3777
  const child = children[i];
3778
+ const declIndex = i;
3778
3779
  let memberName = null;
3779
3780
  if (child.type === "property_declaration") memberName = child.children.find((c) => c.type === "variable_declaration")?.children.find((c) => c.type === "simple_identifier")?.text ?? null;
3780
3781
  else if (child.type === "function_declaration") memberName = child.children.find((c) => c.type === "simple_identifier")?.text ?? null;
@@ -3789,7 +3790,7 @@ function extractKotlinClassMembers(classBody, source) {
3789
3790
  } else break;
3790
3791
  }
3791
3792
  let startIdx = child.startIndex;
3792
- for (let k = i - 1; k >= 0; k--) {
3793
+ for (let k = declIndex - 1; k >= 0; k--) {
3793
3794
  const prev = children[k];
3794
3795
  if (prev.type === "multiline_comment") {
3795
3796
  if (source.slice(prev.startIndex, prev.endIndex).startsWith("/**")) startIdx = prev.startIndex;
@@ -3858,6 +3859,13 @@ const kotlinMergeAdapter = {
3858
3859
  isManagedMember(member) {
3859
3860
  return KOTLIN_MANAGED_ACCESSOR_DOC.test(member.text);
3860
3861
  },
3862
+ importedNames(imp) {
3863
+ const path = imp.text.replace(/^import\s+/, "").trim();
3864
+ const alias = path.match(/\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/);
3865
+ if (alias) return [alias[1]];
3866
+ const last = path.split(".").pop();
3867
+ return last && last !== "*" ? [last] : [];
3868
+ },
3861
3869
  shouldSkipDeepMerge(_symbolName, existingMemberKeys, newMembers) {
3862
3870
  const newText = newMembers.map((m) => m.text).join("\n");
3863
3871
  for (const match of newText.matchAll(/this\.(\w+)/g)) {
@@ -4740,6 +4748,116 @@ function declarationLineStart(content, declStartIndex) {
4740
4748
  return prevNewline === -1 ? 0 : prevNewline + 1;
4741
4749
  }
4742
4750
  /**
4751
+ * Widen a member's byte range so deleting it removes whole lines.
4752
+ *
4753
+ * The raw range starts at the declaration (or its docstring), which sits after
4754
+ * the member's indentation — splicing it out verbatim leaves that indentation
4755
+ * glued to the following line. Extends backwards over the indentation and
4756
+ * forwards over the trailing newline, then absorbs the blank line the removal
4757
+ * would otherwise duplicate (or strand in front of the block's closing brace).
4758
+ */
4759
+ function expandPruneRange(source, rawStart, rawEnd) {
4760
+ let start = rawStart;
4761
+ while (start > 0 && (source[start - 1] === " " || source[start - 1] === " ")) start--;
4762
+ let end = rawEnd;
4763
+ while (end < source.length && (source[end] === " " || source[end] === " ")) end++;
4764
+ end = skipLineBreak(source, end);
4765
+ if (precededByBlankLine(source, start)) {
4766
+ if (startsWithLineBreak(source, end)) end = skipLineBreak(source, end);
4767
+ else if (/^[ \t]*\}/.test(source.slice(end, end + 40))) start = backOverLineBreak(source, start);
4768
+ }
4769
+ return {
4770
+ start,
4771
+ end
4772
+ };
4773
+ }
4774
+ function startsWithLineBreak(source, idx) {
4775
+ return source[idx] === "\n" || source[idx] === "\r" && source[idx + 1] === "\n";
4776
+ }
4777
+ function skipLineBreak(source, idx) {
4778
+ let i = idx;
4779
+ if (source[i] === "\r") i++;
4780
+ return source[i] === "\n" ? i + 1 : i;
4781
+ }
4782
+ function backOverLineBreak(source, idx) {
4783
+ let i = idx;
4784
+ if (source[i - 1] === "\n") i--;
4785
+ if (source[i - 1] === "\r") i--;
4786
+ return i;
4787
+ }
4788
+ /** Whether the line ending immediately before `idx` (a line start) is empty. */
4789
+ function precededByBlankLine(source, idx) {
4790
+ if (idx === 0 || source[idx - 1] !== "\n") return false;
4791
+ const priorLineEnd = backOverLineBreak(source, idx);
4792
+ return priorLineEnd > 0 && source[priorLineEnd - 1] === "\n";
4793
+ }
4794
+ function referencesIdentifier(source, name) {
4795
+ return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(source);
4796
+ }
4797
+ /**
4798
+ * Blank out comments and string text, preserving offsets and line structure.
4799
+ *
4800
+ * An identifier lookup over raw source can't tell a live reference from a name
4801
+ * that merely survives in prose. That distinction decides whether an import
4802
+ * stays: keeping one whose only remaining mention is a KDoc leaves an import of
4803
+ * a class the generator no longer emits, which doesn't compile — the very
4804
+ * failure the prune exists to prevent.
4805
+ *
4806
+ * Interpolated expressions are the exception: `"${Agents.NAME}"` is code that
4807
+ * sits inside a string literal, and masking it would drop an import the file
4808
+ * genuinely uses. String extents are masked around those holes, not over them.
4809
+ *
4810
+ * Grammar node names come from the adapter's `urlFingerprintConfig`, which
4811
+ * already inventories exactly these three categories per language. Comments are
4812
+ * matched structurally instead — every grammar we support names them `*comment`.
4813
+ */
4814
+ function maskNonCode(tree, source, config) {
4815
+ const stringNodeTypes = new Set(config?.stringNodeTypes ?? []);
4816
+ const interpolationNodeTypes = new Set(config?.interpolationNodeTypes ?? []);
4817
+ const spans = [];
4818
+ const collectStringSpans = (node) => {
4819
+ const holes = [];
4820
+ const findHoles = (n) => {
4821
+ if (interpolationNodeTypes.has(n.type)) {
4822
+ holes.push([n.startIndex, n.endIndex]);
4823
+ return;
4824
+ }
4825
+ for (const child of n.children) findHoles(child);
4826
+ };
4827
+ findHoles(node);
4828
+ let cursor = node.startIndex;
4829
+ for (const [holeStart, holeEnd] of holes) {
4830
+ if (holeStart > cursor) spans.push([cursor, holeStart]);
4831
+ cursor = holeEnd;
4832
+ }
4833
+ if (cursor < node.endIndex) spans.push([cursor, node.endIndex]);
4834
+ };
4835
+ const walk = (node) => {
4836
+ if (node.type.includes("comment")) {
4837
+ spans.push([node.startIndex, node.endIndex]);
4838
+ return;
4839
+ }
4840
+ if (stringNodeTypes.has(node.type)) {
4841
+ collectStringSpans(node);
4842
+ return;
4843
+ }
4844
+ for (const child of node.children) walk(child);
4845
+ };
4846
+ walk(tree.rootNode);
4847
+ if (spans.length === 0) return source;
4848
+ let masked = "";
4849
+ let cursor = 0;
4850
+ for (const [spanStart, spanEnd] of spans) {
4851
+ if (spanStart < cursor) continue;
4852
+ masked += source.slice(cursor, spanStart) + source.slice(spanStart, spanEnd).replace(/[^\r\n]/g, " ");
4853
+ cursor = spanEnd;
4854
+ }
4855
+ return masked + source.slice(cursor);
4856
+ }
4857
+ function removeLines(source, lines) {
4858
+ return source.split("\n").filter((line) => !lines.has(line.trim())).join("\n");
4859
+ }
4860
+ /**
4743
4861
  * Resolve a docstring range by searching for the nearest matching text before
4744
4862
  * the declaration start. Falls back to parser-provided offsets when search fails.
4745
4863
  */
@@ -4954,6 +5072,7 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
4954
5072
  let deepAdded = 0;
4955
5073
  let deepPruned = 0;
4956
5074
  const insertions = [];
5075
+ const prunedTexts = [];
4957
5076
  if (adapter.extractMembers) {
4958
5077
  const parser = await getParser(language);
4959
5078
  const resultTree = safeParse(parser, result);
@@ -4982,10 +5101,9 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
4982
5101
  if (pruneEdits.length > 0) {
4983
5102
  pruneEdits.sort((a, b) => b.start - a.start);
4984
5103
  for (const edit of pruneEdits) {
4985
- let end = edit.end;
4986
- while (end < result.length && (result[end] === " " || result[end] === " ")) end++;
4987
- if (result[end] === "\n") end++;
4988
- result = result.slice(0, edit.start) + result.slice(end);
5104
+ const { start, end } = expandPruneRange(result, edit.start, edit.end);
5105
+ prunedTexts.push(result.slice(start, end));
5106
+ result = result.slice(0, start) + result.slice(end);
4989
5107
  }
4990
5108
  deepPruned = pruneEdits.length;
4991
5109
  const reTree = safeParse(parser, result);
@@ -5050,6 +5168,21 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
5050
5168
  result = lines.join("\n");
5051
5169
  }
5052
5170
  }
5171
+ if (prunedTexts.length > 0 && adapter.importedNames) {
5172
+ const prunedText = prunedTexts.join("\n");
5173
+ const codeOnly = maskNonCode(safeParse(await getParser(language), result), result, adapter.urlFingerprintConfig);
5174
+ const orphaned = /* @__PURE__ */ new Set();
5175
+ for (const imp of existingStatements.imports) {
5176
+ const names = adapter.importedNames(imp);
5177
+ if (names.length === 0) continue;
5178
+ if (!names.some((n) => referencesIdentifier(prunedText, n))) continue;
5179
+ const line = imp.text.trim();
5180
+ const withoutImport = removeLines(codeOnly, new Set([line]));
5181
+ if (names.some((n) => referencesIdentifier(withoutImport, n))) continue;
5182
+ orphaned.add(line);
5183
+ }
5184
+ if (orphaned.size > 0) result = removeLines(result, orphaned);
5185
+ }
5053
5186
  let docstringUpdates = 0;
5054
5187
  {
5055
5188
  const parser = await getParser(language);