@hanhnd/agent-kit 1.0.41 → 1.0.43

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.
@@ -1,21 +1,64 @@
1
1
  import * as assert from 'node:assert/strict';
2
2
  import { describe, test } from 'node:test';
3
- import { createLlamaLocalDigestProvider } from '../digest/providers/llama-local.js';
4
- const INPUT = {
5
- sourcePath: '.agent-kit/wiki/archive/conversations/conv_2026-05-19T00-45-17-697Z.md',
6
- contentHash: 'tiny',
7
- content: '**User:** use local digest generation for archived conversations',
8
- };
9
- describe('createLlamaLocalDigestProvider', () => {
10
- test('runs an opt-in smoke test against a real model', { skip: !process.env.AGENT_KIT_DIGEST_MODEL_ID }, async () => {
3
+ import { createLlamaLocalDigestProvider, sanitizeConversationDigestMarkdown, titleFromSource, trimConversationExport, } from '../digest/providers/llama-local.js';
4
+ describe('titleFromSource', () => {
5
+ test('strips the extension and turns separators into spaces', () => {
6
+ assert.equal(titleFromSource('/a/b/conv_claude_1bda-2b2b.md'), 'conv claude 1bda 2b2b');
7
+ });
8
+ test('handles a bare filename with no directory', () => {
9
+ assert.equal(titleFromSource('inbox.md'), 'inbox');
10
+ });
11
+ });
12
+ describe('trimConversationExport', () => {
13
+ test('returns the trimmed content unchanged when within the limit', () => {
14
+ assert.equal(trimConversationExport(' hello world ', 100), 'hello world');
15
+ });
16
+ test('keeps the tail and prepends an omission notice when over the limit', () => {
17
+ const content = 'A'.repeat(50) + 'TAIL_MARKER';
18
+ const out = trimConversationExport(content, 11);
19
+ assert.match(out, /^\[Earlier conversation content omitted/);
20
+ assert.ok(out.endsWith('TAIL_MARKER'), 'keeps the most recent content');
21
+ assert.equal(out.split('\n\n')[1], 'TAIL_MARKER');
22
+ });
23
+ });
24
+ describe('sanitizeConversationDigestMarkdown', () => {
25
+ test('unwraps a fenced markdown block', () => {
26
+ const out = sanitizeConversationDigestMarkdown('```markdown\n# Title\n\nbody\n```');
27
+ assert.equal(out, '# Title\n\nbody\n');
28
+ });
29
+ test('leaves unfenced text intact and guarantees a single trailing newline', () => {
30
+ const out = sanitizeConversationDigestMarkdown(' plain summary ');
31
+ assert.equal(out, 'plain summary\n');
32
+ });
33
+ test('does not strip text that merely mentions a code fence inline', () => {
34
+ const out = sanitizeConversationDigestMarkdown('Use ```bash``` to run it.');
35
+ assert.equal(out, 'Use ```bash``` to run it.\n');
36
+ });
37
+ });
38
+ describe('createLlamaLocalDigestProvider (opt-in real model)', () => {
39
+ const INPUT = {
40
+ sourcePath: '.agent-kit/wiki/raw/conv_2026-05-19T00-45-17-697Z.md',
41
+ contentHash: 'tiny',
42
+ content: '**User:** use local digest generation for archived conversations',
43
+ };
44
+ test('generates a digest with the documented metadata header and a non-empty body', { skip: !process.env.AGENT_KIT_DIGEST_MODEL_ID }, async () => {
11
45
  const modelId = process.env.AGENT_KIT_DIGEST_MODEL_ID;
12
46
  const provider = await createLlamaLocalDigestProvider(modelId);
13
- const output = await provider.generateDigestMarkdown(INPUT, {
14
- modelId,
15
- maxInputChars: 1000,
16
- timeoutMs: 120_000,
17
- });
18
- assert.match(output, /^# Conversation Digest:/);
19
- assert.match(output, /Status: provisional/);
47
+ try {
48
+ const output = await provider.generateDigestMarkdown(INPUT, {
49
+ modelId,
50
+ maxInputChars: 1000,
51
+ timeoutMs: 120_000,
52
+ });
53
+ assert.match(output, /^## Digest: conv 2026/);
54
+ assert.match(output, /\| \*\*Source\*\* \| .*conv_2026-05-19/);
55
+ assert.match(output, new RegExp(`\\| \\*\\*Model\\*\\* \\| ${modelId} \\|`));
56
+ assert.doesNotMatch(output, /<\/?think>/i);
57
+ const body = output.replace(/^##[\s\S]*?\n\n/, '').trim();
58
+ assert.ok(body.length > 0, 'expected a non-empty digest body');
59
+ }
60
+ finally {
61
+ await provider.dispose?.();
62
+ }
20
63
  });
21
64
  });
@@ -8,26 +8,26 @@ class DigestModelRegistryError extends Error {
8
8
  const DIGEST_MODEL_REGISTRY = {
9
9
  [DigestModelId.TINY]: {
10
10
  id: DigestModelId.TINY,
11
- ggufUri: 'hf:bartowski/Qwen2.5-0.5B-Instruct-GGUF/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf',
12
- approxSizeBytes: 400_000_000,
11
+ ggufUri: 'hf:lmstudio-community/Qwen3.5-0.8B-GGUF/Qwen3.5-0.8B-Q4_K_M.gguf',
12
+ approxSizeBytes: 528_000_000,
13
13
  license: 'Apache-2.0',
14
- sourceUrl: 'https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF',
14
+ sourceUrl: 'https://huggingface.co/lmstudio-community/Qwen3.5-0.8B-GGUF',
15
15
  enabled: true,
16
16
  },
17
17
  [DigestModelId.BASE]: {
18
18
  id: DigestModelId.BASE,
19
- ggufUri: 'hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf',
20
- approxSizeBytes: 1_000_000_000,
19
+ ggufUri: 'hf:lmstudio-community/Qwen3.5-2B-GGUF/Qwen3.5-2B-Q4_K_M.gguf',
20
+ approxSizeBytes: 1_270_000_000,
21
21
  license: 'Apache-2.0',
22
- sourceUrl: 'https://huggingface.co/bartowski/Qwen2.5-1.5B-Instruct-GGUF',
22
+ sourceUrl: 'https://huggingface.co/lmstudio-community/Qwen3.5-2B-GGUF',
23
23
  enabled: true,
24
24
  },
25
25
  [DigestModelId.LARGE]: {
26
26
  id: DigestModelId.LARGE,
27
- ggufUri: 'hf:bartowski/Qwen2.5-3B-Instruct-GGUF/Qwen2.5-3B-Instruct-Q4_K_M.gguf',
28
- approxSizeBytes: 1_900_000_000,
27
+ ggufUri: 'hf:lmstudio-community/Qwen3.5-4B-GGUF/Qwen3.5-4B-Q4_K_M.gguf',
28
+ approxSizeBytes: 2_710_000_000,
29
29
  license: 'Apache-2.0',
30
- sourceUrl: 'https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF',
30
+ sourceUrl: 'https://huggingface.co/lmstudio-community/Qwen3.5-4B-GGUF',
31
31
  enabled: true,
32
32
  },
33
33
  };
@@ -1,2 +1,5 @@
1
1
  import type { ConversationDigestProvider } from '../types.js';
2
+ export declare function titleFromSource(sourcePath: string): string;
3
+ export declare function trimConversationExport(content: string, maxInputChars: number): string;
4
+ export declare function sanitizeConversationDigestMarkdown(generatedText: string): string;
2
5
  export declare function createLlamaLocalDigestProvider(modelId: string): Promise<ConversationDigestProvider>;
@@ -11,13 +11,13 @@ class LlamaLocalDigestProviderError extends Error {
11
11
  this.name = 'LlamaLocalDigestProviderError';
12
12
  }
13
13
  }
14
- function titleFromSource(sourcePath) {
14
+ export function titleFromSource(sourcePath) {
15
15
  return path
16
16
  .basename(sourcePath)
17
17
  .replace(/\.[^.]+$/, '')
18
18
  .replace(/[_-]+/g, ' ');
19
19
  }
20
- function trimConversationExport(content, maxInputChars) {
20
+ export function trimConversationExport(content, maxInputChars) {
21
21
  const trimmed = content.trim();
22
22
  if (trimmed.length <= maxInputChars)
23
23
  return trimmed;
@@ -54,7 +54,7 @@ function stripCodeFence(text) {
54
54
  const match = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);
55
55
  return match ? match[1].trim() : trimmed;
56
56
  }
57
- function sanitizeConversationDigestMarkdown(generatedText) {
57
+ export function sanitizeConversationDigestMarkdown(generatedText) {
58
58
  return stripCodeFence(generatedText).trim() + '\n';
59
59
  }
60
60
  export async function createLlamaLocalDigestProvider(modelId) {
@@ -104,6 +104,7 @@ export async function createLlamaLocalDigestProvider(modelId) {
104
104
  const response = await withTimeout(session.prompt(userContent, {
105
105
  maxTokens: LLAMA_MAX_GENERATED_TOKENS,
106
106
  temperature: LLAMA_TEMPERATURE,
107
+ budgets: { thoughtTokens: 0 },
107
108
  }), options.timeoutMs, () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`));
108
109
  const metadata = `## Digest: ${titleFromSource(input.sourcePath)}
109
110
 
@@ -9,7 +9,7 @@
9
9
  * File format:
10
10
  * [default]
11
11
  * ATLASSIAN_USER_EMAIL = user@example.com
12
- * ATLASSIAN_API_TOKEN = your_token_here
12
+ * JIRA_API_TOKEN = your_token_here
13
13
  *
14
14
  * Active profile: KIT_PROFILE env var (defaults to "default")
15
15
  */
@@ -9,7 +9,7 @@
9
9
  * File format:
10
10
  * [default]
11
11
  * ATLASSIAN_USER_EMAIL = user@example.com
12
- * ATLASSIAN_API_TOKEN = your_token_here
12
+ * JIRA_API_TOKEN = your_token_here
13
13
  *
14
14
  * Active profile: KIT_PROFILE env var (defaults to "default")
15
15
  */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Confluence storage-format (XHTML) → markdown converter.
3
+ *
4
+ * Pure module: no I/O, no network, no MCP coupling. Three stages — a single forward
5
+ * tokenizer pass, a tolerant tree builder, then a recursive renderer.
6
+ */
7
+ /** Max tree depth before rendering emits a truncation marker. */
8
+ export declare const MAX_STORAGE_DEPTH = 100;
9
+ /** Visible truncation marker emitted at MAX_STORAGE_DEPTH. */
10
+ export declare const DEPTH_TRUNCATION_MARKER = "[content nested too deeply - truncated]";
11
+ /**
12
+ * Convert Confluence storage-format XHTML into markdown.
13
+ *
14
+ * Never throws for any string input, including malformed XHTML, unbalanced tags, stray
15
+ * CDATA, and the empty string. Returns '' for input with no content — the caller decides
16
+ * how to present emptiness.
17
+ */
18
+ export declare function storageToMarkdown(xhtml: string | null | undefined): string;