@kkeve/suite 0.0.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 (63) hide show
  1. package/dist/agent/factory.d.ts +25 -0
  2. package/dist/agent/factory.js +99 -0
  3. package/dist/agent/index.d.ts +3 -0
  4. package/dist/agent/index.js +7 -0
  5. package/dist/agent/loader.d.ts +22 -0
  6. package/dist/agent/loader.js +117 -0
  7. package/dist/agent/types.d.ts +9 -0
  8. package/dist/agent/types.js +2 -0
  9. package/dist/agent-loop/complete-task.d.ts +28 -0
  10. package/dist/agent-loop/complete-task.js +70 -0
  11. package/dist/agent-loop/index.d.ts +16 -0
  12. package/dist/agent-loop/index.js +26 -0
  13. package/dist/agent-loop/loop.d.ts +42 -0
  14. package/dist/agent-loop/loop.js +282 -0
  15. package/dist/agent-loop/runtime.d.ts +40 -0
  16. package/dist/agent-loop/runtime.js +163 -0
  17. package/dist/agent-loop/types.d.ts +161 -0
  18. package/dist/agent-loop/types.js +105 -0
  19. package/dist/context/index.d.ts +8 -0
  20. package/dist/context/index.js +29 -0
  21. package/dist/context/tokenCalculation.d.ts +55 -0
  22. package/dist/context/tokenCalculation.js +95 -0
  23. package/dist/context/truncation.d.ts +67 -0
  24. package/dist/context/truncation.js +142 -0
  25. package/dist/context/types.d.ts +7 -0
  26. package/dist/context/types.js +27 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.js +45 -0
  29. package/dist/llm/OpenAIClient.d.ts +31 -0
  30. package/dist/llm/OpenAIClient.js +455 -0
  31. package/dist/llm/constants.d.ts +2 -0
  32. package/dist/llm/constants.js +6 -0
  33. package/dist/llm/errors.d.ts +25 -0
  34. package/dist/llm/errors.js +51 -0
  35. package/dist/llm/index.d.ts +49 -0
  36. package/dist/llm/index.js +158 -0
  37. package/dist/llm/types.d.ts +124 -0
  38. package/dist/llm/types.js +2 -0
  39. package/dist/llm/utils.d.ts +29 -0
  40. package/dist/llm/utils.js +284 -0
  41. package/dist/skill/frontmatter-parser.d.ts +50 -0
  42. package/dist/skill/frontmatter-parser.js +152 -0
  43. package/dist/skill/index.d.ts +12 -0
  44. package/dist/skill/index.js +17 -0
  45. package/dist/skill/loader.d.ts +96 -0
  46. package/dist/skill/loader.js +256 -0
  47. package/dist/tools/ask-user.d.ts +8 -0
  48. package/dist/tools/ask-user.js +47 -0
  49. package/dist/tools/bash.d.ts +47 -0
  50. package/dist/tools/bash.js +213 -0
  51. package/dist/tools/edit-file.d.ts +9 -0
  52. package/dist/tools/edit-file.js +76 -0
  53. package/dist/tools/glob.d.ts +8 -0
  54. package/dist/tools/glob.js +101 -0
  55. package/dist/tools/grep.d.ts +10 -0
  56. package/dist/tools/grep.js +76 -0
  57. package/dist/tools/index.d.ts +24 -0
  58. package/dist/tools/index.js +47 -0
  59. package/dist/tools/read-files.d.ts +8 -0
  60. package/dist/tools/read-files.js +81 -0
  61. package/dist/tools/write-file.d.ts +8 -0
  62. package/dist/tools/write-file.js +63 -0
  63. package/package.json +59 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Loop Core Types
4
+ *
5
+ * Reference: gemini-cli LocalAgentExecutor + AgentProtocol
6
+ * Adapted for OpenAI-compatible API (internal proxy at xxx)
7
+ *
8
+ * Key differences from gemini-cli:
9
+ * - LLM calls use OpenAIClient instead of GeminiChat
10
+ * - Tool definitions reuse core/llm/types.ts Tool interface
11
+ * - No Gemini-specific Content/Part types — use OpenAI Message format
12
+ * - Simplified timeout (no DeadlineTimer pause/resume — we don't wait for user confirmation)
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ToolRegistry = exports.TM_ERROR_NO_COMPLETE_TASK = exports.TM_ERROR = exports.TM_ABORTED = exports.TM_MAX_TURNS = exports.TM_TIMEOUT = exports.TM_GOAL = exports.COMPLETE_TASK_TOOL_NAME = exports.DEFAULT_MAX_TIME_MINUTES = exports.DEFAULT_MAX_TURNS = void 0;
16
+ // ===== Constants (reference: gemini-cli DEFAULT_MAX_TURNS/DEFAULT_MAX_TIME_MINUTES) =====
17
+ /** Default maximum number of conversational turns for an agent. */
18
+ exports.DEFAULT_MAX_TURNS = 30;
19
+ /** Default maximum execution time for an agent in minutes. */
20
+ exports.DEFAULT_MAX_TIME_MINUTES = 10;
21
+ /** Tool name for signaling task completion (reference: gemini-cli CompleteTaskTool) */
22
+ exports.COMPLETE_TASK_TOOL_NAME = 'complete_task';
23
+ // ===== Termination Modes =====
24
+ /**
25
+ * Describes how an agent's execution ended.
26
+ * Reference: gemini-cli AgentTerminateMode (agents/types.ts L24-31)
27
+ */
28
+ // Inline termination mode constants (vite tree-shaking removes cross-module const objects)
29
+ exports.TM_GOAL = 'GOAL';
30
+ exports.TM_TIMEOUT = 'TIMEOUT';
31
+ exports.TM_MAX_TURNS = 'MAX_TURNS';
32
+ exports.TM_ABORTED = 'ABORTED';
33
+ exports.TM_ERROR = 'ERROR';
34
+ exports.TM_ERROR_NO_COMPLETE_TASK = 'ERROR_NO_COMPLETE_TASK';
35
+ /**
36
+ * Tool registry for managing and executing tools during agent execution.
37
+ * Reference: gemini-cli ToolRegistry (tools/tool-registry.ts)
38
+ *
39
+ * Simplified: no message bus, no confirmation policy, no sort, no MCP integration.
40
+ * These can be added incrementally.
41
+ */
42
+ class ToolRegistry {
43
+ tools = new Map();
44
+ /** Register a tool definition. */
45
+ register(tool) {
46
+ this.tools.set(tool.name, tool);
47
+ }
48
+ /** Unregister a tool by name. */
49
+ unregister(name) {
50
+ this.tools.delete(name);
51
+ }
52
+ /** Get all registered tool names. */
53
+ getAllToolNames() {
54
+ return Array.from(this.tools.keys());
55
+ }
56
+ /** Convert registered tools to the core/llm/types.ts Tool map format for LLM calls. */
57
+ getToolsMap() {
58
+ const result = {};
59
+ for (const [name, def] of this.tools) {
60
+ result[name] = {
61
+ description: def.description,
62
+ inputSchema: def.inputSchema,
63
+ execute: def.execute,
64
+ customJsonSchema: def.customJsonSchema,
65
+ };
66
+ }
67
+ return result;
68
+ }
69
+ /** Get a subset of tools by name (for agent's toolNames restriction). */
70
+ getToolsMapSubset(names) {
71
+ const result = {};
72
+ for (const name of names) {
73
+ const def = this.tools.get(name);
74
+ if (def) {
75
+ result[name] = {
76
+ description: def.description,
77
+ inputSchema: def.inputSchema,
78
+ execute: def.execute,
79
+ customJsonSchema: def.customJsonSchema,
80
+ };
81
+ }
82
+ }
83
+ return result;
84
+ }
85
+ /** Execute a registered tool by name with the given arguments and optional context. */
86
+ async execute(name, args, context) {
87
+ const tool = this.tools.get(name);
88
+ if (!tool) {
89
+ throw new Error(`Tool not found: ${name}`);
90
+ }
91
+ // Validate args with zod schema
92
+ const validation = tool.inputSchema.safeParse(args);
93
+ const validatedArgs = validation.success ? validation.data : args;
94
+ return tool.execute(validatedArgs, context);
95
+ }
96
+ /** Check if a tool is registered. */
97
+ has(name) {
98
+ return this.tools.has(name);
99
+ }
100
+ /** Get a tool definition by name. */
101
+ get(name) {
102
+ return this.tools.get(name);
103
+ }
104
+ }
105
+ exports.ToolRegistry = ToolRegistry;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @ks-data/agent-core — context module entry point.
3
+ *
4
+ * Provides truncation and token estimation utilities for managing
5
+ * LLM input size within token budgets.
6
+ */
7
+ export { ASCII_TOKENS_PER_CHAR, NON_ASCII_TOKENS_PER_CHAR, MSG_OVERHEAD_TOKENS, DEFAULT_CHARS_PER_TOKEN, estimateTextTokens, estimateTokenCountSync, estimateTokenCountForText, calculateTokenCount, type TokenEstimationPart, } from './tokenCalculation';
8
+ export { MIN_TARGET_TOKENS, MIN_CHARS_FOR_TRUNCATION, TEXT_TRUNCATION_PREFIX, TOOL_TRUNCATION_PREFIX, estimateCharsFromTokens, truncateProportionally, normalizeFunctionResponse, truncateToTokenBudget, } from './truncation';
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * @ks-data/agent-core — context module entry point.
4
+ *
5
+ * Provides truncation and token estimation utilities for managing
6
+ * LLM input size within token budgets.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.truncateToTokenBudget = exports.normalizeFunctionResponse = exports.truncateProportionally = exports.estimateCharsFromTokens = exports.TOOL_TRUNCATION_PREFIX = exports.TEXT_TRUNCATION_PREFIX = exports.MIN_CHARS_FOR_TRUNCATION = exports.MIN_TARGET_TOKENS = exports.calculateTokenCount = exports.estimateTokenCountForText = exports.estimateTokenCountSync = exports.estimateTextTokens = exports.DEFAULT_CHARS_PER_TOKEN = exports.MSG_OVERHEAD_TOKENS = exports.NON_ASCII_TOKENS_PER_CHAR = exports.ASCII_TOKENS_PER_CHAR = void 0;
10
+ var tokenCalculation_1 = require("./tokenCalculation");
11
+ // Token calculation
12
+ Object.defineProperty(exports, "ASCII_TOKENS_PER_CHAR", { enumerable: true, get: function () { return tokenCalculation_1.ASCII_TOKENS_PER_CHAR; } });
13
+ Object.defineProperty(exports, "NON_ASCII_TOKENS_PER_CHAR", { enumerable: true, get: function () { return tokenCalculation_1.NON_ASCII_TOKENS_PER_CHAR; } });
14
+ Object.defineProperty(exports, "MSG_OVERHEAD_TOKENS", { enumerable: true, get: function () { return tokenCalculation_1.MSG_OVERHEAD_TOKENS; } });
15
+ Object.defineProperty(exports, "DEFAULT_CHARS_PER_TOKEN", { enumerable: true, get: function () { return tokenCalculation_1.DEFAULT_CHARS_PER_TOKEN; } });
16
+ Object.defineProperty(exports, "estimateTextTokens", { enumerable: true, get: function () { return tokenCalculation_1.estimateTextTokens; } });
17
+ Object.defineProperty(exports, "estimateTokenCountSync", { enumerable: true, get: function () { return tokenCalculation_1.estimateTokenCountSync; } });
18
+ Object.defineProperty(exports, "estimateTokenCountForText", { enumerable: true, get: function () { return tokenCalculation_1.estimateTokenCountForText; } });
19
+ Object.defineProperty(exports, "calculateTokenCount", { enumerable: true, get: function () { return tokenCalculation_1.calculateTokenCount; } });
20
+ var truncation_1 = require("./truncation");
21
+ // Truncation
22
+ Object.defineProperty(exports, "MIN_TARGET_TOKENS", { enumerable: true, get: function () { return truncation_1.MIN_TARGET_TOKENS; } });
23
+ Object.defineProperty(exports, "MIN_CHARS_FOR_TRUNCATION", { enumerable: true, get: function () { return truncation_1.MIN_CHARS_FOR_TRUNCATION; } });
24
+ Object.defineProperty(exports, "TEXT_TRUNCATION_PREFIX", { enumerable: true, get: function () { return truncation_1.TEXT_TRUNCATION_PREFIX; } });
25
+ Object.defineProperty(exports, "TOOL_TRUNCATION_PREFIX", { enumerable: true, get: function () { return truncation_1.TOOL_TRUNCATION_PREFIX; } });
26
+ Object.defineProperty(exports, "estimateCharsFromTokens", { enumerable: true, get: function () { return truncation_1.estimateCharsFromTokens; } });
27
+ Object.defineProperty(exports, "truncateProportionally", { enumerable: true, get: function () { return truncation_1.truncateProportionally; } });
28
+ Object.defineProperty(exports, "normalizeFunctionResponse", { enumerable: true, get: function () { return truncation_1.normalizeFunctionResponse; } });
29
+ Object.defineProperty(exports, "truncateToTokenBudget", { enumerable: true, get: function () { return truncation_1.truncateToTokenBudget; } });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Token estimation constants — ported from gemini-cli (Apache-2.0).
3
+ *
4
+ * ASCII characters (0-127) are roughly 3-4 chars per token.
5
+ * We use 0.33 (~3 chars/token) as a conservative baseline for mixed text and code.
6
+ */
7
+ export declare const ASCII_TOKENS_PER_CHAR = 0.33;
8
+ /**
9
+ * Non-ASCII characters (including CJK) are often 1-2 tokens per char.
10
+ * We use 1.5 as a conservative estimate to avoid underestimation.
11
+ */
12
+ export declare const NON_ASCII_TOKENS_PER_CHAR = 1.5;
13
+ /** Structural overhead per message turn (role prefixes, separators). */
14
+ export declare const MSG_OVERHEAD_TOKENS = 5;
15
+ /** Default chars-per-token ratio used as fallback. */
16
+ export declare const DEFAULT_CHARS_PER_TOKEN = 4;
17
+ /**
18
+ * Heuristic estimation of tokens for a text string.
19
+ * Uses char-by-char scan for strings ≤ 100K chars, length/charsPerToken for larger strings.
20
+ */
21
+ export declare function estimateTextTokens(text: string, charsPerToken?: number): number;
22
+ /**
23
+ * Synchronous token-count estimation for an array of message-like parts.
24
+ * Adapted from gemini-cli's `estimateTokenCountSync`, but uses our own
25
+ * message-part representation instead of Gemini's `Part` type.
26
+ *
27
+ * Each "part" can be:
28
+ * - `{ text: string }` → text token estimation
29
+ * - `{ functionResponse: ... }` → tool response estimation
30
+ * - other objects → JSON string length / charsPerToken fallback
31
+ */
32
+ export interface TokenEstimationPart {
33
+ text?: string;
34
+ functionResponse?: {
35
+ name?: string;
36
+ response?: unknown;
37
+ };
38
+ [key: string]: unknown;
39
+ }
40
+ export declare function estimateTokenCountSync(parts: TokenEstimationPart[], charsPerToken?: number): number;
41
+ /**
42
+ * Asynchronous token count calculation for a text string or message array.
43
+ * For text-only content, this simply delegates to `estimateTokenCountSync`.
44
+ * For content with media references (future: image/PDF), we would call
45
+ * an external tokenizer — but for now, everything is text-only estimation.
46
+ *
47
+ * This replaces gemini-cli's `calculateRequestTokenCount` which uses
48
+ * the Gemini `countTokens` API. We don't have an equivalent API for
49
+ * our internal OpenAI-compatible proxy, so we rely on heuristic estimation.
50
+ */
51
+ export declare function calculateTokenCount(content: string | TokenEstimationPart[], charsPerToken?: number): Promise<number>;
52
+ /**
53
+ * Convenience: estimate token count for a single text string.
54
+ */
55
+ export declare function estimateTokenCountForText(text: string, charsPerToken?: number): number;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CHARS_PER_TOKEN = exports.MSG_OVERHEAD_TOKENS = exports.NON_ASCII_TOKENS_PER_CHAR = exports.ASCII_TOKENS_PER_CHAR = void 0;
4
+ exports.estimateTextTokens = estimateTextTokens;
5
+ exports.estimateTokenCountSync = estimateTokenCountSync;
6
+ exports.calculateTokenCount = calculateTokenCount;
7
+ exports.estimateTokenCountForText = estimateTokenCountForText;
8
+ /**
9
+ * Token estimation constants — ported from gemini-cli (Apache-2.0).
10
+ *
11
+ * ASCII characters (0-127) are roughly 3-4 chars per token.
12
+ * We use 0.33 (~3 chars/token) as a conservative baseline for mixed text and code.
13
+ */
14
+ exports.ASCII_TOKENS_PER_CHAR = 0.33;
15
+ /**
16
+ * Non-ASCII characters (including CJK) are often 1-2 tokens per char.
17
+ * We use 1.5 as a conservative estimate to avoid underestimation.
18
+ */
19
+ exports.NON_ASCII_TOKENS_PER_CHAR = 1.5;
20
+ /** Structural overhead per message turn (role prefixes, separators). */
21
+ exports.MSG_OVERHEAD_TOKENS = 5;
22
+ /** Default chars-per-token ratio used as fallback. */
23
+ exports.DEFAULT_CHARS_PER_TOKEN = 4;
24
+ /**
25
+ * Maximum number of characters to process with the full character-by-character heuristic.
26
+ * Above this, we use a faster approximation to avoid performance bottlenecks.
27
+ */
28
+ const MAX_CHARS_FOR_FULL_HEURISTIC = 100_000;
29
+ /**
30
+ * Heuristic estimation of tokens for a text string.
31
+ * Uses char-by-char scan for strings ≤ 100K chars, length/charsPerToken for larger strings.
32
+ */
33
+ function estimateTextTokens(text, charsPerToken = exports.DEFAULT_CHARS_PER_TOKEN) {
34
+ if (text.length > MAX_CHARS_FOR_FULL_HEURISTIC) {
35
+ return text.length / charsPerToken;
36
+ }
37
+ let tokens = 0;
38
+ const asciiTokensPerChar = 1 / charsPerToken;
39
+ for (let i = 0; i < text.length; i++) {
40
+ if (text.charCodeAt(i) <= 127) {
41
+ tokens += asciiTokensPerChar;
42
+ }
43
+ else {
44
+ tokens += exports.NON_ASCII_TOKENS_PER_CHAR;
45
+ }
46
+ }
47
+ return tokens;
48
+ }
49
+ function estimateTokenCountSync(parts, charsPerToken = exports.DEFAULT_CHARS_PER_TOKEN) {
50
+ let totalTokens = 0;
51
+ for (const part of parts) {
52
+ if (typeof part.text === 'string') {
53
+ totalTokens += estimateTextTokens(part.text, charsPerToken);
54
+ }
55
+ else if (part.functionResponse) {
56
+ const fr = part.functionResponse;
57
+ let frTokens = (fr.name?.length ?? 0) / charsPerToken;
58
+ const response = fr.response;
59
+ if (typeof response === 'string') {
60
+ frTokens += response.length / charsPerToken;
61
+ }
62
+ else if (response !== undefined && response !== null) {
63
+ frTokens += JSON.stringify(response).length / charsPerToken;
64
+ }
65
+ totalTokens += frTokens;
66
+ }
67
+ else {
68
+ // Fallback: JSON-serialize the whole part and estimate by length.
69
+ totalTokens += JSON.stringify(part).length / charsPerToken;
70
+ }
71
+ }
72
+ return Math.floor(totalTokens);
73
+ }
74
+ /**
75
+ * Asynchronous token count calculation for a text string or message array.
76
+ * For text-only content, this simply delegates to `estimateTokenCountSync`.
77
+ * For content with media references (future: image/PDF), we would call
78
+ * an external tokenizer — but for now, everything is text-only estimation.
79
+ *
80
+ * This replaces gemini-cli's `calculateRequestTokenCount` which uses
81
+ * the Gemini `countTokens` API. We don't have an equivalent API for
82
+ * our internal OpenAI-compatible proxy, so we rely on heuristic estimation.
83
+ */
84
+ async function calculateTokenCount(content, charsPerToken = exports.DEFAULT_CHARS_PER_TOKEN) {
85
+ if (typeof content === 'string') {
86
+ return estimateTokenCountSync([{ text: content }], charsPerToken);
87
+ }
88
+ return estimateTokenCountSync(content, charsPerToken);
89
+ }
90
+ /**
91
+ * Convenience: estimate token count for a single text string.
92
+ */
93
+ function estimateTokenCountForText(text, charsPerToken = exports.DEFAULT_CHARS_PER_TOKEN) {
94
+ return Math.floor(estimateTextTokens(text, charsPerToken));
95
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Truncation utilities — ported from gemini-cli (Apache-2.0), adapted for
3
+ * our project's message format (OpenAI-compatible instead of Gemini Part).
4
+ *
5
+ * Key differences from the original:
6
+ * - No `Part` type from `@google/genai` → use `TokenEstimationPart` from tokenCalculation
7
+ * - `normalizeFunctionResponse` works on plain JSON objects instead of Gemini's Part structure
8
+ */
9
+ /** Minimum target tokens to preserve when truncating. */
10
+ export declare const MIN_TARGET_TOKENS = 10;
11
+ /** Minimum character count before truncation is applied. */
12
+ export declare const MIN_CHARS_FOR_TRUNCATION = 100;
13
+ /** Prefix marker for truncated text content. */
14
+ export declare const TEXT_TRUNCATION_PREFIX = "[Message Normalized: Exceeded size limit]";
15
+ /** Prefix marker for truncated tool output. */
16
+ export declare const TOOL_TRUNCATION_PREFIX = "[Message Normalized: Tool output exceeded size limit]";
17
+ /**
18
+ * Estimates the character limit for a target token count, accounting for ASCII vs Non-ASCII.
19
+ * Uses a weighted average based on the provided text to decide how many characters
20
+ * fit into the target token budget.
21
+ *
22
+ * Ported from gemini-cli's `estimateCharsFromTokens`.
23
+ */
24
+ export declare function estimateCharsFromTokens(text: string, targetTokens: number): number;
25
+ /**
26
+ * Truncates a string to a target length, keeping a proportional amount of the head and tail,
27
+ * and prepending a prefix marker.
28
+ *
29
+ * By default, 20% of the budget goes to the head (beginning) and 80% to the tail (end),
30
+ * which preserves recent context while keeping some original context.
31
+ *
32
+ * Ported from gemini-cli's `truncateProportionally`.
33
+ */
34
+ export declare function truncateProportionally(str: string, targetChars: number, prefix?: string, headRatio?: number): string;
35
+ /**
36
+ * Safely normalizes a function/tool response by truncating large string values
37
+ * within the response object while maintaining its JSON structure (preserving keys
38
+ * like stdout, stderr, etc.).
39
+ *
40
+ * This is the OpenAI-compatible adaptation of gemini-cli's `normalizeFunctionResponse`.
41
+ * Instead of operating on Gemini's `Part.functionResponse`, it operates on plain
42
+ * JSON objects (the `result` field of OpenAI tool_call responses).
43
+ *
44
+ * @param responseObj - The tool response object (e.g., { stdout: "...", stderr: "..." })
45
+ * @param ratio - Retention ratio (e.g., 0.3 means keep ~30% of tokens)
46
+ * @param headRatio - Proportion of head content to keep (default 0.2)
47
+ * @param savedPath - Optional path where full output was saved (added as footer)
48
+ * @param intentSummary - Optional LLM-generated summary appended after truncation
49
+ * @returns The normalized response object with large values truncated
50
+ */
51
+ export declare function normalizeFunctionResponse(responseObj: Record<string, unknown>, ratio: number, headRatio?: number, savedPath?: string, intentSummary?: string): Record<string, unknown>;
52
+ /**
53
+ * Truncate a text string to fit within a token budget.
54
+ * Converts the token budget to a character budget using `estimateCharsFromTokens`,
55
+ * then applies `truncateProportionally`.
56
+ *
57
+ * This is a convenience function combining token estimation + truncation,
58
+ * useful for truncating LLM input (a11y tree snapshots, PRD content, etc.)
59
+ * to fit within the model's token limit.
60
+ *
61
+ * @param text - The text to potentially truncate
62
+ * @param maxTokens - Maximum token budget
63
+ * @param prefix - Prefix marker for truncated text
64
+ * @param headRatio - Proportion of head to keep
65
+ * @returns Truncated text if exceeding budget, original text otherwise
66
+ */
67
+ export declare function truncateToTokenBudget(text: string, maxTokens: number, prefix?: string, headRatio?: number): string;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * Truncation utilities — ported from gemini-cli (Apache-2.0), adapted for
4
+ * our project's message format (OpenAI-compatible instead of Gemini Part).
5
+ *
6
+ * Key differences from the original:
7
+ * - No `Part` type from `@google/genai` → use `TokenEstimationPart` from tokenCalculation
8
+ * - `normalizeFunctionResponse` works on plain JSON objects instead of Gemini's Part structure
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.TOOL_TRUNCATION_PREFIX = exports.TEXT_TRUNCATION_PREFIX = exports.MIN_CHARS_FOR_TRUNCATION = exports.MIN_TARGET_TOKENS = void 0;
12
+ exports.estimateCharsFromTokens = estimateCharsFromTokens;
13
+ exports.truncateProportionally = truncateProportionally;
14
+ exports.normalizeFunctionResponse = normalizeFunctionResponse;
15
+ exports.truncateToTokenBudget = truncateToTokenBudget;
16
+ const tokenCalculation_1 = require("./tokenCalculation");
17
+ /** Minimum target tokens to preserve when truncating. */
18
+ exports.MIN_TARGET_TOKENS = 10;
19
+ /** Minimum character count before truncation is applied. */
20
+ exports.MIN_CHARS_FOR_TRUNCATION = 100;
21
+ /** Prefix marker for truncated text content. */
22
+ exports.TEXT_TRUNCATION_PREFIX = '[Message Normalized: Exceeded size limit]';
23
+ /** Prefix marker for truncated tool output. */
24
+ exports.TOOL_TRUNCATION_PREFIX = '[Message Normalized: Tool output exceeded size limit]';
25
+ /**
26
+ * Estimates the character limit for a target token count, accounting for ASCII vs Non-ASCII.
27
+ * Uses a weighted average based on the provided text to decide how many characters
28
+ * fit into the target token budget.
29
+ *
30
+ * Ported from gemini-cli's `estimateCharsFromTokens`.
31
+ */
32
+ function estimateCharsFromTokens(text, targetTokens) {
33
+ if (text.length === 0)
34
+ return 0;
35
+ // Count ASCII vs Non-ASCII in a sample of the text.
36
+ let asciiCount = 0;
37
+ const sampleLen = Math.min(text.length, 1000);
38
+ for (let i = 0; i < sampleLen; i++) {
39
+ if (text.charCodeAt(i) <= 127) {
40
+ asciiCount++;
41
+ }
42
+ }
43
+ const asciiRatio = asciiCount / sampleLen;
44
+ // Weighted tokens per character:
45
+ const avgTokensPerChar = asciiRatio * 0.33 + (1 - asciiRatio) * 1.5; // ASCII_TOKENS_PER_CHAR + NON_ASCII_TOKENS_PER_CHAR
46
+ // Characters = Tokens / (Tokens per Character)
47
+ return Math.floor(targetTokens / avgTokensPerChar);
48
+ }
49
+ /**
50
+ * Truncates a string to a target length, keeping a proportional amount of the head and tail,
51
+ * and prepending a prefix marker.
52
+ *
53
+ * By default, 20% of the budget goes to the head (beginning) and 80% to the tail (end),
54
+ * which preserves recent context while keeping some original context.
55
+ *
56
+ * Ported from gemini-cli's `truncateProportionally`.
57
+ */
58
+ function truncateProportionally(str, targetChars, prefix = exports.TEXT_TRUNCATION_PREFIX, headRatio = 0.2) {
59
+ if (str.length <= targetChars)
60
+ return str;
61
+ const ellipsis = '\n...\n';
62
+ const overhead = prefix.length + ellipsis.length + 1; // +1 for newline after prefix
63
+ const availableChars = Math.max(0, targetChars - overhead);
64
+ if (availableChars <= 0) {
65
+ return prefix; // Safe fallback if target is extremely small
66
+ }
67
+ const headChars = Math.floor(availableChars * headRatio);
68
+ const tailChars = availableChars - headChars;
69
+ return `${prefix}\n${str.substring(0, headChars)}${ellipsis}${str.substring(str.length - tailChars)}`;
70
+ }
71
+ /**
72
+ * Safely normalizes a function/tool response by truncating large string values
73
+ * within the response object while maintaining its JSON structure (preserving keys
74
+ * like stdout, stderr, etc.).
75
+ *
76
+ * This is the OpenAI-compatible adaptation of gemini-cli's `normalizeFunctionResponse`.
77
+ * Instead of operating on Gemini's `Part.functionResponse`, it operates on plain
78
+ * JSON objects (the `result` field of OpenAI tool_call responses).
79
+ *
80
+ * @param responseObj - The tool response object (e.g., { stdout: "...", stderr: "..." })
81
+ * @param ratio - Retention ratio (e.g., 0.3 means keep ~30% of tokens)
82
+ * @param headRatio - Proportion of head content to keep (default 0.2)
83
+ * @param savedPath - Optional path where full output was saved (added as footer)
84
+ * @param intentSummary - Optional LLM-generated summary appended after truncation
85
+ * @returns The normalized response object with large values truncated
86
+ */
87
+ function normalizeFunctionResponse(responseObj, ratio, headRatio = 0.2, savedPath, intentSummary) {
88
+ if (typeof responseObj !== 'object' || responseObj === null) {
89
+ return responseObj;
90
+ }
91
+ let hasChanges = false;
92
+ const newResponse = {};
93
+ for (const [key, value] of Object.entries(responseObj)) {
94
+ if (typeof value === 'string' && value.length > exports.MIN_CHARS_FOR_TRUNCATION) {
95
+ const valueTokens = (0, tokenCalculation_1.estimateTokenCountSync)([{ text: value }]);
96
+ const targetValueTokens = Math.max(exports.MIN_TARGET_TOKENS, Math.floor(valueTokens * ratio));
97
+ const targetChars = estimateCharsFromTokens(value, targetValueTokens);
98
+ if (value.length > targetChars) {
99
+ let truncated = truncateProportionally(value, targetChars, exports.TOOL_TRUNCATION_PREFIX, headRatio);
100
+ if (savedPath) {
101
+ truncated += `\n\nFull output saved to: ${savedPath}`;
102
+ }
103
+ if (intentSummary) {
104
+ truncated += intentSummary;
105
+ }
106
+ newResponse[key] = truncated;
107
+ hasChanges = true;
108
+ }
109
+ else {
110
+ newResponse[key] = value;
111
+ }
112
+ }
113
+ else {
114
+ newResponse[key] = value;
115
+ }
116
+ }
117
+ return hasChanges ? newResponse : responseObj;
118
+ }
119
+ /**
120
+ * Truncate a text string to fit within a token budget.
121
+ * Converts the token budget to a character budget using `estimateCharsFromTokens`,
122
+ * then applies `truncateProportionally`.
123
+ *
124
+ * This is a convenience function combining token estimation + truncation,
125
+ * useful for truncating LLM input (a11y tree snapshots, PRD content, etc.)
126
+ * to fit within the model's token limit.
127
+ *
128
+ * @param text - The text to potentially truncate
129
+ * @param maxTokens - Maximum token budget
130
+ * @param prefix - Prefix marker for truncated text
131
+ * @param headRatio - Proportion of head to keep
132
+ * @returns Truncated text if exceeding budget, original text otherwise
133
+ */
134
+ function truncateToTokenBudget(text, maxTokens, prefix = exports.TEXT_TRUNCATION_PREFIX, headRatio = 0.2) {
135
+ if (text.length <= exports.MIN_CHARS_FOR_TRUNCATION)
136
+ return text;
137
+ const estimatedTokens = (0, tokenCalculation_1.estimateTokenCountSync)([{ text }]);
138
+ if (estimatedTokens <= maxTokens)
139
+ return text;
140
+ const targetChars = estimateCharsFromTokens(text, maxTokens);
141
+ return truncateProportionally(text, targetChars, prefix, headRatio);
142
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core context module types — shared between truncation, token calculation,
3
+ * and future context manager/compression modules.
4
+ */
5
+ export type { TokenEstimationPart, } from './tokenCalculation';
6
+ export { ASCII_TOKENS_PER_CHAR, NON_ASCII_TOKENS_PER_CHAR, MSG_OVERHEAD_TOKENS, DEFAULT_CHARS_PER_TOKEN, estimateTextTokens, estimateTokenCountSync, estimateTokenCountForText, calculateTokenCount, } from './tokenCalculation';
7
+ export { MIN_TARGET_TOKENS, MIN_CHARS_FOR_TRUNCATION, TEXT_TRUNCATION_PREFIX, TOOL_TRUNCATION_PREFIX, estimateCharsFromTokens, truncateProportionally, normalizeFunctionResponse, truncateToTokenBudget, } from './truncation';
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Core context module types — shared between truncation, token calculation,
4
+ * and future context manager/compression modules.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.truncateToTokenBudget = exports.normalizeFunctionResponse = exports.truncateProportionally = exports.estimateCharsFromTokens = exports.TOOL_TRUNCATION_PREFIX = exports.TEXT_TRUNCATION_PREFIX = exports.MIN_CHARS_FOR_TRUNCATION = exports.MIN_TARGET_TOKENS = exports.calculateTokenCount = exports.estimateTokenCountForText = exports.estimateTokenCountSync = exports.estimateTextTokens = exports.DEFAULT_CHARS_PER_TOKEN = exports.MSG_OVERHEAD_TOKENS = exports.NON_ASCII_TOKENS_PER_CHAR = exports.ASCII_TOKENS_PER_CHAR = void 0;
8
+ var tokenCalculation_1 = require("./tokenCalculation");
9
+ // Token calculation
10
+ Object.defineProperty(exports, "ASCII_TOKENS_PER_CHAR", { enumerable: true, get: function () { return tokenCalculation_1.ASCII_TOKENS_PER_CHAR; } });
11
+ Object.defineProperty(exports, "NON_ASCII_TOKENS_PER_CHAR", { enumerable: true, get: function () { return tokenCalculation_1.NON_ASCII_TOKENS_PER_CHAR; } });
12
+ Object.defineProperty(exports, "MSG_OVERHEAD_TOKENS", { enumerable: true, get: function () { return tokenCalculation_1.MSG_OVERHEAD_TOKENS; } });
13
+ Object.defineProperty(exports, "DEFAULT_CHARS_PER_TOKEN", { enumerable: true, get: function () { return tokenCalculation_1.DEFAULT_CHARS_PER_TOKEN; } });
14
+ Object.defineProperty(exports, "estimateTextTokens", { enumerable: true, get: function () { return tokenCalculation_1.estimateTextTokens; } });
15
+ Object.defineProperty(exports, "estimateTokenCountSync", { enumerable: true, get: function () { return tokenCalculation_1.estimateTokenCountSync; } });
16
+ Object.defineProperty(exports, "estimateTokenCountForText", { enumerable: true, get: function () { return tokenCalculation_1.estimateTokenCountForText; } });
17
+ Object.defineProperty(exports, "calculateTokenCount", { enumerable: true, get: function () { return tokenCalculation_1.calculateTokenCount; } });
18
+ var truncation_1 = require("./truncation");
19
+ // Truncation
20
+ Object.defineProperty(exports, "MIN_TARGET_TOKENS", { enumerable: true, get: function () { return truncation_1.MIN_TARGET_TOKENS; } });
21
+ Object.defineProperty(exports, "MIN_CHARS_FOR_TRUNCATION", { enumerable: true, get: function () { return truncation_1.MIN_CHARS_FOR_TRUNCATION; } });
22
+ Object.defineProperty(exports, "TEXT_TRUNCATION_PREFIX", { enumerable: true, get: function () { return truncation_1.TEXT_TRUNCATION_PREFIX; } });
23
+ Object.defineProperty(exports, "TOOL_TRUNCATION_PREFIX", { enumerable: true, get: function () { return truncation_1.TOOL_TRUNCATION_PREFIX; } });
24
+ Object.defineProperty(exports, "estimateCharsFromTokens", { enumerable: true, get: function () { return truncation_1.estimateCharsFromTokens; } });
25
+ Object.defineProperty(exports, "truncateProportionally", { enumerable: true, get: function () { return truncation_1.truncateProportionally; } });
26
+ Object.defineProperty(exports, "normalizeFunctionResponse", { enumerable: true, get: function () { return truncation_1.normalizeFunctionResponse; } });
27
+ Object.defineProperty(exports, "truncateToTokenBudget", { enumerable: true, get: function () { return truncation_1.truncateToTokenBudget; } });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Core module — runtime infrastructure for the agent
3
+ *
4
+ * Layers:
5
+ * - llm/ — OpenAI-compatible LLM client (invoke + chat)
6
+ * - context/ — Token calculation + truncation (from gemini-cli)
7
+ * - agent-loop/ — Universal agent execution engine (while loop + LLM + tools)
8
+ * - skill/ — SKILL.md discovery, loading, and execution (Progressive Disclosure)
9
+ * - tools/ — Built-in tool library (bash, codegraph, git, keve-read-doc, etc.)
10
+ */
11
+ export { LLM, InvokeError, InvokeErrorType } from './llm/index';
12
+ export type { ChatResult, ContentItem, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool, } from './llm/index';
13
+ export { estimateCharsFromTokens, truncateProportionally, normalizeFunctionResponse, estimateTokenCountSync, } from './context/index';
14
+ export type { TokenEstimationPart } from './context/index';
15
+ export { AgentLoop } from './agent-loop/index';
16
+ export type { AgentDefinition, AgentEvent, AgentEventCallback, AgentLoopContext, AgentResult, AgentTerminateMode, ToolDefinition, } from './agent-loop/index';
17
+ export { COMPLETE_TASK_TOOL_NAME, DEFAULT_MAX_TURNS, DEFAULT_MAX_TIME_MINUTES, createCompleteTaskTool, ToolRegistry, detectRuntimeEnvironment, } from './agent-loop/index';
18
+ export type { RuntimeEnvironment, ToolExecutionContext, CompleteTaskToolOptions } from './agent-loop/index';
19
+ export { AgentLoader, AgentFactory } from './agent/index';
20
+ export type { AgentRegistryDefinition } from './agent/index';
21
+ export { SkillLoader } from './skill/index';
22
+ export type { SkillDefinition } from './skill/index';
23
+ export { createDefaultToolRegistry, getBuiltinToolNames } from './tools/index';
package/dist/index.js ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Core module — runtime infrastructure for the agent
4
+ *
5
+ * Layers:
6
+ * - llm/ — OpenAI-compatible LLM client (invoke + chat)
7
+ * - context/ — Token calculation + truncation (from gemini-cli)
8
+ * - agent-loop/ — Universal agent execution engine (while loop + LLM + tools)
9
+ * - skill/ — SKILL.md discovery, loading, and execution (Progressive Disclosure)
10
+ * - tools/ — Built-in tool library (bash, codegraph, git, keve-read-doc, etc.)
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.getBuiltinToolNames = exports.createDefaultToolRegistry = exports.SkillLoader = exports.AgentFactory = exports.AgentLoader = exports.detectRuntimeEnvironment = exports.ToolRegistry = exports.createCompleteTaskTool = exports.DEFAULT_MAX_TIME_MINUTES = exports.DEFAULT_MAX_TURNS = exports.COMPLETE_TASK_TOOL_NAME = exports.AgentLoop = exports.estimateTokenCountSync = exports.normalizeFunctionResponse = exports.truncateProportionally = exports.estimateCharsFromTokens = exports.InvokeErrorType = exports.InvokeError = exports.LLM = void 0;
14
+ // LLM
15
+ var index_1 = require("./llm/index");
16
+ Object.defineProperty(exports, "LLM", { enumerable: true, get: function () { return index_1.LLM; } });
17
+ Object.defineProperty(exports, "InvokeError", { enumerable: true, get: function () { return index_1.InvokeError; } });
18
+ Object.defineProperty(exports, "InvokeErrorType", { enumerable: true, get: function () { return index_1.InvokeErrorType; } });
19
+ // Context
20
+ var index_2 = require("./context/index");
21
+ Object.defineProperty(exports, "estimateCharsFromTokens", { enumerable: true, get: function () { return index_2.estimateCharsFromTokens; } });
22
+ Object.defineProperty(exports, "truncateProportionally", { enumerable: true, get: function () { return index_2.truncateProportionally; } });
23
+ Object.defineProperty(exports, "normalizeFunctionResponse", { enumerable: true, get: function () { return index_2.normalizeFunctionResponse; } });
24
+ Object.defineProperty(exports, "estimateTokenCountSync", { enumerable: true, get: function () { return index_2.estimateTokenCountSync; } });
25
+ // Agent Loop
26
+ var index_3 = require("./agent-loop/index");
27
+ Object.defineProperty(exports, "AgentLoop", { enumerable: true, get: function () { return index_3.AgentLoop; } });
28
+ var index_4 = require("./agent-loop/index");
29
+ Object.defineProperty(exports, "COMPLETE_TASK_TOOL_NAME", { enumerable: true, get: function () { return index_4.COMPLETE_TASK_TOOL_NAME; } });
30
+ Object.defineProperty(exports, "DEFAULT_MAX_TURNS", { enumerable: true, get: function () { return index_4.DEFAULT_MAX_TURNS; } });
31
+ Object.defineProperty(exports, "DEFAULT_MAX_TIME_MINUTES", { enumerable: true, get: function () { return index_4.DEFAULT_MAX_TIME_MINUTES; } });
32
+ Object.defineProperty(exports, "createCompleteTaskTool", { enumerable: true, get: function () { return index_4.createCompleteTaskTool; } });
33
+ Object.defineProperty(exports, "ToolRegistry", { enumerable: true, get: function () { return index_4.ToolRegistry; } });
34
+ Object.defineProperty(exports, "detectRuntimeEnvironment", { enumerable: true, get: function () { return index_4.detectRuntimeEnvironment; } });
35
+ // Agent
36
+ var index_5 = require("./agent/index");
37
+ Object.defineProperty(exports, "AgentLoader", { enumerable: true, get: function () { return index_5.AgentLoader; } });
38
+ Object.defineProperty(exports, "AgentFactory", { enumerable: true, get: function () { return index_5.AgentFactory; } });
39
+ // Skill
40
+ var index_6 = require("./skill/index");
41
+ Object.defineProperty(exports, "SkillLoader", { enumerable: true, get: function () { return index_6.SkillLoader; } });
42
+ // Tools (general tools, aligned with gemini-cli)
43
+ var index_7 = require("./tools/index");
44
+ Object.defineProperty(exports, "createDefaultToolRegistry", { enumerable: true, get: function () { return index_7.createDefaultToolRegistry; } });
45
+ Object.defineProperty(exports, "getBuiltinToolNames", { enumerable: true, get: function () { return index_7.getBuiltinToolNames; } });
@@ -0,0 +1,31 @@
1
+ import type { ChatResult, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types';
2
+ /**
3
+ * Client for OpenAI compatible APIs
4
+ */
5
+ export declare class OpenAIClient implements LLMClient {
6
+ config: Required<LLMConfig>;
7
+ private fetch;
8
+ constructor(config: Required<LLMConfig>);
9
+ invoke(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal, options?: InvokeOptions): Promise<InvokeResult>;
10
+ /**
11
+ * Attempt to recover a valid JSON object from a malformed LLM response.
12
+ * Handles common LLM JSON errors:
13
+ * 1. Extra text after closing brace (e.g. "...}{extra")
14
+ * 2. Trailing commas inside objects
15
+ * 3. Unbalanced braces — find the deepest matching pair
16
+ * 4. Mixed quote styles (single quotes instead of double quotes)
17
+ */
18
+ private tryRecoverJSON;
19
+ /**
20
+ * Call the LLM and return the full assistant message without executing any tools.
21
+ * Used by the agent loop to get the LLM's response and process tool_calls separately.
22
+ *
23
+ * This method:
24
+ * 1. Sends messages + tools to the OpenAI-compatible API
25
+ * 2. Returns the full assistant Message (with content + tool_calls)
26
+ * 3. Does NOT execute any tools or validate tool args
27
+ *
28
+ * The agent loop handles tool execution via its own ToolRegistry.
29
+ */
30
+ chat(messages: Message[], tools: Record<string, Tool>, abortSignal?: AbortSignal, options?: InvokeOptions): Promise<ChatResult>;
31
+ }