@open330/oac-budget 2026.3.2 → 2026.3.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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type AgentProviderId = "claude-code" | "codex
|
|
1
|
+
type AgentProviderId = "claude-code" | "codex" | "opencode" | string;
|
|
2
2
|
type TaskSource = "lint" | "todo" | "test-gap" | "dead-code" | "github-issue" | "custom";
|
|
3
3
|
type TaskComplexity = "trivial" | "simple" | "moderate" | "complex";
|
|
4
4
|
type ExecutionMode = "new-pr" | "update-pr" | "direct-commit";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/complexity.ts","../src/estimator.ts","../src/providers/claude-counter.ts","../src/providers/codex-counter.ts","../src/planner.ts"],"sourcesContent":["import type { Task, TaskComplexity, TaskSource } from \"./estimator.js\";\n\nconst SOURCE_LOC_BASELINE: Record<TaskSource, number> = {\n lint: 8,\n todo: 16,\n \"test-gap\": 48,\n \"dead-code\": 36,\n \"github-issue\": 88,\n custom: 40,\n};\n\nconst SOURCE_COMPLEXITY_SCORE: Record<TaskSource, number> = {\n lint: 0,\n todo: 0,\n \"test-gap\": 1,\n \"dead-code\": 1,\n \"github-issue\": 2,\n custom: 1,\n};\n\nconst ESTIMATED_LOC_KEYS = [\n \"estimatedLoc\",\n \"estimatedLOC\",\n \"estimatedLocChanges\",\n \"estimatedDiffSize\",\n \"loc\",\n \"locChanges\",\n \"linesChanged\",\n \"lineCount\",\n \"diffSize\",\n \"changeSize\",\n] as const;\n\nfunction parseNumericValue(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n return value;\n }\n\n if (typeof value === \"string\") {\n const parsed = Number.parseFloat(value);\n if (Number.isFinite(parsed) && parsed >= 0) {\n return parsed;\n }\n }\n\n return undefined;\n}\n\nfunction readMetadataLocEstimate(metadata: Record<string, unknown>): number | undefined {\n for (const key of ESTIMATED_LOC_KEYS) {\n const directValue = parseNumericValue(metadata[key]);\n if (directValue !== undefined) {\n return directValue;\n }\n }\n\n const metrics = metadata.metrics;\n if (metrics && typeof metrics === \"object\" && !Array.isArray(metrics)) {\n const metricsRecord = metrics as Record<string, unknown>;\n for (const key of ESTIMATED_LOC_KEYS) {\n const metricValue = parseNumericValue(metricsRecord[key]);\n if (metricValue !== undefined) {\n return metricValue;\n }\n }\n }\n\n return undefined;\n}\n\nexport function estimateLocChanges(task: Task): number {\n const metadataEstimate = readMetadataLocEstimate(task.metadata);\n if (metadataEstimate !== undefined) {\n return Math.max(1, Math.round(metadataEstimate));\n }\n\n const sourceBaseline = SOURCE_LOC_BASELINE[task.source] ?? SOURCE_LOC_BASELINE.custom;\n const fileAdjustment = Math.max(task.targetFiles.length, 1) * 8;\n\n return Math.max(sourceBaseline, fileAdjustment);\n}\n\nexport function analyzeTaskComplexity(task: Task): TaskComplexity {\n const fileCount = task.targetFiles.length;\n const locChanges = estimateLocChanges(task);\n\n const fileScore = fileCount <= 1 ? 0 : fileCount <= 3 ? 1 : fileCount <= 6 ? 2 : 3;\n const locScore = locChanges <= 20 ? 0 : locChanges <= 80 ? 1 : locChanges <= 200 ? 2 : 3;\n const sourceScore = SOURCE_COMPLEXITY_SCORE[task.source] ?? SOURCE_COMPLEXITY_SCORE.custom;\n\n const totalScore = fileScore + locScore + sourceScore;\n\n if (totalScore <= 1) {\n return \"trivial\";\n }\n\n if (totalScore <= 3) {\n return \"simple\";\n }\n\n if (totalScore <= 6) {\n return \"moderate\";\n }\n\n return \"complex\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { analyzeTaskComplexity } from \"./complexity.js\";\nimport { ClaudeTokenCounter } from \"./providers/claude-counter.js\";\nimport { CodexTokenCounter } from \"./providers/codex-counter.js\";\n\nexport type AgentProviderId = \"claude-code\" | \"codex-cli\" | \"opencode\" | string;\n\nexport type TaskSource = \"lint\" | \"todo\" | \"test-gap\" | \"dead-code\" | \"github-issue\" | \"custom\";\n\nexport type TaskComplexity = \"trivial\" | \"simple\" | \"moderate\" | \"complex\";\n\nexport type ExecutionMode = \"new-pr\" | \"update-pr\" | \"direct-commit\";\n\nexport interface Task {\n id: string;\n source: TaskSource;\n title: string;\n description: string;\n targetFiles: string[];\n priority: number;\n complexity: TaskComplexity;\n executionMode: ExecutionMode;\n linkedIssue?: {\n number: number;\n url: string;\n labels: string[];\n };\n metadata: Record<string, unknown>;\n discoveredAt: string;\n}\n\nexport interface TokenEstimate {\n taskId: string;\n providerId: AgentProviderId;\n contextTokens: number;\n promptTokens: number;\n expectedOutputTokens: number;\n totalEstimatedTokens: number;\n confidence: number;\n feasible: boolean;\n estimatedCostUsd?: number;\n}\n\nexport interface TokenCounter {\n countTokens(text: string): number;\n readonly invocationOverhead: number;\n readonly maxContextTokens: number;\n}\n\ninterface TokenCountResult {\n tokens: number;\n usedFallback: boolean;\n}\n\ninterface ContextFileResult extends TokenCountResult {\n missing: boolean;\n}\n\nconst ESTIMATION_PADDING_MULTIPLIER = 1.2;\nconst FALLBACK_CONFIDENCE = 0.5;\n\nconst COMPLEXITY_MULTIPLIERS: Record<TaskComplexity, number> = {\n trivial: 0.5,\n simple: 1,\n moderate: 2,\n complex: 3.5,\n};\n\nconst COMPLEXITY_CONFIDENCE: Record<TaskComplexity, number> = {\n trivial: 0.9,\n simple: 0.75,\n moderate: 0.6,\n complex: 0.4,\n};\n\nconst COMPLEXITY_ORDER: Record<TaskComplexity, number> = {\n trivial: 0,\n simple: 1,\n moderate: 2,\n complex: 3,\n};\n\nconst claudeCounter = new ClaudeTokenCounter();\nconst codexCounter = new CodexTokenCounter();\n\nfunction getTokenCounter(provider: AgentProviderId): TokenCounter {\n if (provider === \"claude-code\") {\n return claudeCounter;\n }\n\n return codexCounter;\n}\n\nfunction approximateTokenCount(text: string): number {\n if (text.length === 0) {\n return 0;\n }\n\n return Math.max(1, Math.ceil(text.length / 4));\n}\n\nfunction countTokensWithFallback(text: string, counter: TokenCounter): TokenCountResult {\n try {\n return {\n tokens: counter.countTokens(text),\n usedFallback: false,\n };\n } catch {\n return {\n tokens: approximateTokenCount(text),\n usedFallback: true,\n };\n }\n}\n\nfunction chooseConservativeComplexity(\n declaredComplexity: TaskComplexity,\n analyzedComplexity: TaskComplexity,\n): TaskComplexity {\n return COMPLEXITY_ORDER[declaredComplexity] >= COMPLEXITY_ORDER[analyzedComplexity]\n ? declaredComplexity\n : analyzedComplexity;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n if (value < min) {\n return min;\n }\n\n if (value > max) {\n return max;\n }\n\n return value;\n}\n\nfunction resolveTargetFilePath(targetFile: string): string {\n return isAbsolute(targetFile) ? targetFile : resolve(process.cwd(), targetFile);\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n const serialized = JSON.stringify(value);\n return serialized ?? \"null\";\n } catch {\n return \"[unserializable]\";\n }\n}\n\nasync function readContextFile(\n targetFile: string,\n counter: TokenCounter,\n): Promise<ContextFileResult> {\n const resolvedPath = resolveTargetFilePath(targetFile);\n\n try {\n const content = await readFile(resolvedPath, \"utf8\");\n const counted = countTokensWithFallback(content, counter);\n\n return {\n ...counted,\n missing: false,\n };\n } catch {\n return {\n tokens: 0,\n usedFallback: false,\n missing: true,\n };\n }\n}\n\nexport async function estimateTokens(\n task: Task,\n provider: AgentProviderId,\n): Promise<TokenEstimate> {\n const counter = getTokenCounter(provider);\n const uniqueTargetFiles = [...new Set(task.targetFiles)];\n\n const fileResults = await Promise.all(\n uniqueTargetFiles.map((targetFile) => readContextFile(targetFile, counter)),\n );\n\n const repoStructureSeed = uniqueTargetFiles.join(\"\\n\");\n const repoStructureCount = countTokensWithFallback(repoStructureSeed, counter);\n\n const contextTokens =\n repoStructureCount.tokens + fileResults.reduce((sum, result) => sum + result.tokens, 0);\n\n const promptSeed = [\n `Task ID: ${task.id}`,\n `Title: ${task.title}`,\n `Source: ${task.source}`,\n `Priority: ${task.priority}`,\n `Description:\\n${task.description}`,\n `Target Files:\\n${uniqueTargetFiles.join(\"\\n\") || \"(none)\"}`,\n `Metadata: ${safeStringify(task.metadata)}`,\n ].join(\"\\n\\n\");\n\n const promptContentCount = countTokensWithFallback(promptSeed, counter);\n const promptTokens = counter.invocationOverhead + promptContentCount.tokens;\n\n const analyzedComplexity = analyzeTaskComplexity(task);\n const effectiveComplexity = chooseConservativeComplexity(task.complexity, analyzedComplexity);\n const expectedOutputTokens = Math.ceil(\n contextTokens * COMPLEXITY_MULTIPLIERS[effectiveComplexity],\n );\n\n const rawTotalTokens = contextTokens + promptTokens + expectedOutputTokens;\n const totalEstimatedTokens = Math.ceil(rawTotalTokens * ESTIMATION_PADDING_MULTIPLIER);\n\n const usedFallback =\n repoStructureCount.usedFallback ||\n promptContentCount.usedFallback ||\n fileResults.some((result) => result.usedFallback);\n\n const missingFileCount = fileResults.filter((result) => result.missing).length;\n\n let confidence = COMPLEXITY_CONFIDENCE[effectiveComplexity];\n if (usedFallback) {\n confidence = Math.min(confidence, FALLBACK_CONFIDENCE);\n }\n\n if (missingFileCount > 0) {\n confidence -= Math.min(0.25, missingFileCount * 0.05);\n }\n\n if (uniqueTargetFiles.length === 0) {\n confidence -= 0.1;\n }\n\n if (task.complexity !== analyzedComplexity) {\n confidence -= 0.05;\n }\n\n const feasible = totalEstimatedTokens <= counter.maxContextTokens;\n\n return {\n taskId: task.id,\n providerId: provider,\n contextTokens,\n promptTokens,\n expectedOutputTokens,\n totalEstimatedTokens,\n confidence: clamp(confidence, 0.1, 0.95),\n feasible,\n };\n}\n","import { type Tiktoken, get_encoding } from \"tiktoken\";\n\nconst CLAUDE_ENCODING = \"cl100k_base\";\nconst CLAUDE_INVOCATION_OVERHEAD = 1_500;\nconst CLAUDE_MAX_CONTEXT_TOKENS = 200_000;\n\nlet encoder: Tiktoken | undefined;\n\nfunction getEncoder(): Tiktoken {\n encoder ??= get_encoding(CLAUDE_ENCODING);\n return encoder;\n}\n\nexport class ClaudeTokenCounter {\n readonly invocationOverhead = CLAUDE_INVOCATION_OVERHEAD;\n readonly maxContextTokens = CLAUDE_MAX_CONTEXT_TOKENS;\n readonly encoding = CLAUDE_ENCODING;\n\n countTokens(text: string): number {\n return getEncoder().encode(text).length;\n }\n}\n","import { type Tiktoken, get_encoding } from \"tiktoken\";\n\nconst CODEX_INVOCATION_OVERHEAD = 1_000;\nconst CODEX_MAX_CONTEXT_TOKENS = 200_000;\nconst PRIMARY_ENCODING = \"o200k_base\";\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\ntype SupportedCodexEncoding = typeof PRIMARY_ENCODING | typeof FALLBACK_ENCODING;\n\nlet encoder: Tiktoken | undefined;\nlet selectedEncoding: SupportedCodexEncoding | undefined;\n\nfunction getEncoder(): Tiktoken {\n if (encoder) {\n return encoder;\n }\n\n try {\n encoder = get_encoding(PRIMARY_ENCODING);\n selectedEncoding = PRIMARY_ENCODING;\n } catch {\n encoder = get_encoding(FALLBACK_ENCODING);\n selectedEncoding = FALLBACK_ENCODING;\n }\n\n return encoder;\n}\n\nexport class CodexTokenCounter {\n readonly invocationOverhead = CODEX_INVOCATION_OVERHEAD;\n readonly maxContextTokens = CODEX_MAX_CONTEXT_TOKENS;\n\n get encoding(): SupportedCodexEncoding {\n getEncoder();\n return selectedEncoding ?? FALLBACK_ENCODING;\n }\n\n countTokens(text: string): number {\n return getEncoder().encode(text).length;\n }\n}\n","import type { AgentProviderId, Task, TokenEstimate } from \"./estimator.js\";\n\nconst DEFAULT_RESERVE_PERCENT = 0.1;\nconst MIN_CONFIDENCE_THRESHOLD = 0.5;\nconst TOO_COMPLEX_BUDGET_SHARE = 0.6;\n\ntype DeferredReason = \"budget_exceeded\" | \"low_confidence\" | \"too_complex\";\n\nexport interface ExecutionPlan {\n totalBudget: number;\n selectedTasks: Array<{\n task: Task;\n estimate: TokenEstimate;\n cumulativeBudgetUsed: number;\n }>;\n deferredTasks: Array<{\n task: Task;\n estimate: TokenEstimate;\n reason: DeferredReason;\n }>;\n reserveTokens: number;\n remainingTokens: number;\n}\n\ninterface CandidateTask {\n task: Task;\n estimate: TokenEstimate;\n}\n\nfunction normalizeBudget(budget: number): number {\n if (!Number.isFinite(budget) || budget <= 0) {\n return 0;\n }\n\n return Math.floor(budget);\n}\n\nfunction getFallbackEstimate(task: Task): TokenEstimate {\n return {\n taskId: task.id,\n providerId: \"unknown\" as AgentProviderId,\n contextTokens: 0,\n promptTokens: 0,\n expectedOutputTokens: 0,\n totalEstimatedTokens: Number.MAX_SAFE_INTEGER,\n confidence: 0,\n feasible: false,\n };\n}\n\nfunction classifyDeferredReason(\n task: Task,\n estimate: TokenEstimate,\n effectiveBudget: number,\n): DeferredReason | undefined {\n if (!estimate.feasible) {\n return \"budget_exceeded\";\n }\n\n if (estimate.confidence < MIN_CONFIDENCE_THRESHOLD) {\n return \"low_confidence\";\n }\n\n if (\n task.complexity === \"complex\" &&\n effectiveBudget > 0 &&\n estimate.totalEstimatedTokens > effectiveBudget * TOO_COMPLEX_BUDGET_SHARE\n ) {\n return \"too_complex\";\n }\n\n return undefined;\n}\n\nfunction scoreByPriorityPerToken(task: Task, estimate: TokenEstimate): number {\n if (estimate.totalEstimatedTokens <= 0) {\n return task.priority;\n }\n\n return task.priority / estimate.totalEstimatedTokens;\n}\n\nexport function buildExecutionPlan(\n tasks: Task[],\n estimates: Map<string, TokenEstimate>,\n budget: number,\n): ExecutionPlan {\n const totalBudget = normalizeBudget(budget);\n const reserveTokens = Math.floor(totalBudget * DEFAULT_RESERVE_PERCENT);\n const effectiveBudget = Math.max(0, totalBudget - reserveTokens);\n\n const deferredTasks: ExecutionPlan[\"deferredTasks\"] = [];\n const candidates: CandidateTask[] = [];\n\n for (const task of tasks) {\n const estimate = estimates.get(task.id) ?? getFallbackEstimate(task);\n const reason = classifyDeferredReason(task, estimate, effectiveBudget);\n\n if (reason) {\n deferredTasks.push({ task, estimate, reason });\n continue;\n }\n\n candidates.push({ task, estimate });\n }\n\n candidates.sort((left, right) => {\n const ratioDifference =\n scoreByPriorityPerToken(right.task, right.estimate) -\n scoreByPriorityPerToken(left.task, left.estimate);\n\n if (ratioDifference !== 0) {\n return ratioDifference;\n }\n\n const priorityDifference = right.task.priority - left.task.priority;\n if (priorityDifference !== 0) {\n return priorityDifference;\n }\n\n return left.estimate.totalEstimatedTokens - right.estimate.totalEstimatedTokens;\n });\n\n const selectedTasks: ExecutionPlan[\"selectedTasks\"] = [];\n let budgetUsed = 0;\n\n for (const candidate of candidates) {\n const nextBudgetUsed = budgetUsed + candidate.estimate.totalEstimatedTokens;\n\n if (nextBudgetUsed <= effectiveBudget) {\n budgetUsed = nextBudgetUsed;\n selectedTasks.push({\n task: candidate.task,\n estimate: candidate.estimate,\n cumulativeBudgetUsed: budgetUsed,\n });\n continue;\n }\n\n deferredTasks.push({\n task: candidate.task,\n estimate: candidate.estimate,\n reason: \"budget_exceeded\",\n });\n }\n\n return {\n totalBudget,\n selectedTasks,\n deferredTasks,\n reserveTokens,\n remainingTokens: Math.max(0, effectiveBudget - budgetUsed),\n };\n}\n"],"mappings":"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","names":["get_encoding","encoder","getEncoder"]}
|
|
1
|
+
{"version":3,"sources":["../src/complexity.ts","../src/estimator.ts","../src/providers/claude-counter.ts","../src/providers/codex-counter.ts","../src/planner.ts"],"sourcesContent":["import type { Task, TaskComplexity, TaskSource } from \"./estimator.js\";\n\nconst SOURCE_LOC_BASELINE: Record<TaskSource, number> = {\n lint: 8,\n todo: 16,\n \"test-gap\": 48,\n \"dead-code\": 36,\n \"github-issue\": 88,\n custom: 40,\n};\n\nconst SOURCE_COMPLEXITY_SCORE: Record<TaskSource, number> = {\n lint: 0,\n todo: 0,\n \"test-gap\": 1,\n \"dead-code\": 1,\n \"github-issue\": 2,\n custom: 1,\n};\n\nconst ESTIMATED_LOC_KEYS = [\n \"estimatedLoc\",\n \"estimatedLOC\",\n \"estimatedLocChanges\",\n \"estimatedDiffSize\",\n \"loc\",\n \"locChanges\",\n \"linesChanged\",\n \"lineCount\",\n \"diffSize\",\n \"changeSize\",\n] as const;\n\nfunction parseNumericValue(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value) && value >= 0) {\n return value;\n }\n\n if (typeof value === \"string\") {\n const parsed = Number.parseFloat(value);\n if (Number.isFinite(parsed) && parsed >= 0) {\n return parsed;\n }\n }\n\n return undefined;\n}\n\nfunction readMetadataLocEstimate(metadata: Record<string, unknown>): number | undefined {\n for (const key of ESTIMATED_LOC_KEYS) {\n const directValue = parseNumericValue(metadata[key]);\n if (directValue !== undefined) {\n return directValue;\n }\n }\n\n const metrics = metadata.metrics;\n if (metrics && typeof metrics === \"object\" && !Array.isArray(metrics)) {\n const metricsRecord = metrics as Record<string, unknown>;\n for (const key of ESTIMATED_LOC_KEYS) {\n const metricValue = parseNumericValue(metricsRecord[key]);\n if (metricValue !== undefined) {\n return metricValue;\n }\n }\n }\n\n return undefined;\n}\n\nexport function estimateLocChanges(task: Task): number {\n const metadataEstimate = readMetadataLocEstimate(task.metadata);\n if (metadataEstimate !== undefined) {\n return Math.max(1, Math.round(metadataEstimate));\n }\n\n const sourceBaseline = SOURCE_LOC_BASELINE[task.source] ?? SOURCE_LOC_BASELINE.custom;\n const fileAdjustment = Math.max(task.targetFiles.length, 1) * 8;\n\n return Math.max(sourceBaseline, fileAdjustment);\n}\n\nexport function analyzeTaskComplexity(task: Task): TaskComplexity {\n const fileCount = task.targetFiles.length;\n const locChanges = estimateLocChanges(task);\n\n const fileScore = fileCount <= 1 ? 0 : fileCount <= 3 ? 1 : fileCount <= 6 ? 2 : 3;\n const locScore = locChanges <= 20 ? 0 : locChanges <= 80 ? 1 : locChanges <= 200 ? 2 : 3;\n const sourceScore = SOURCE_COMPLEXITY_SCORE[task.source] ?? SOURCE_COMPLEXITY_SCORE.custom;\n\n const totalScore = fileScore + locScore + sourceScore;\n\n if (totalScore <= 1) {\n return \"trivial\";\n }\n\n if (totalScore <= 3) {\n return \"simple\";\n }\n\n if (totalScore <= 6) {\n return \"moderate\";\n }\n\n return \"complex\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { analyzeTaskComplexity } from \"./complexity.js\";\nimport { ClaudeTokenCounter } from \"./providers/claude-counter.js\";\nimport { CodexTokenCounter } from \"./providers/codex-counter.js\";\n\nexport type AgentProviderId = \"claude-code\" | \"codex\" | \"opencode\" | string;\n\nexport type TaskSource = \"lint\" | \"todo\" | \"test-gap\" | \"dead-code\" | \"github-issue\" | \"custom\";\n\nexport type TaskComplexity = \"trivial\" | \"simple\" | \"moderate\" | \"complex\";\n\nexport type ExecutionMode = \"new-pr\" | \"update-pr\" | \"direct-commit\";\n\nexport interface Task {\n id: string;\n source: TaskSource;\n title: string;\n description: string;\n targetFiles: string[];\n priority: number;\n complexity: TaskComplexity;\n executionMode: ExecutionMode;\n linkedIssue?: {\n number: number;\n url: string;\n labels: string[];\n };\n metadata: Record<string, unknown>;\n discoveredAt: string;\n}\n\nexport interface TokenEstimate {\n taskId: string;\n providerId: AgentProviderId;\n contextTokens: number;\n promptTokens: number;\n expectedOutputTokens: number;\n totalEstimatedTokens: number;\n confidence: number;\n feasible: boolean;\n estimatedCostUsd?: number;\n}\n\nexport interface TokenCounter {\n countTokens(text: string): number;\n readonly invocationOverhead: number;\n readonly maxContextTokens: number;\n}\n\ninterface TokenCountResult {\n tokens: number;\n usedFallback: boolean;\n}\n\ninterface ContextFileResult extends TokenCountResult {\n missing: boolean;\n}\n\nconst ESTIMATION_PADDING_MULTIPLIER = 1.2;\nconst FALLBACK_CONFIDENCE = 0.5;\n\nconst COMPLEXITY_MULTIPLIERS: Record<TaskComplexity, number> = {\n trivial: 0.5,\n simple: 1,\n moderate: 2,\n complex: 3.5,\n};\n\nconst COMPLEXITY_CONFIDENCE: Record<TaskComplexity, number> = {\n trivial: 0.9,\n simple: 0.75,\n moderate: 0.6,\n complex: 0.4,\n};\n\nconst COMPLEXITY_ORDER: Record<TaskComplexity, number> = {\n trivial: 0,\n simple: 1,\n moderate: 2,\n complex: 3,\n};\n\nconst claudeCounter = new ClaudeTokenCounter();\nconst codexCounter = new CodexTokenCounter();\n\nfunction getTokenCounter(provider: AgentProviderId): TokenCounter {\n if (provider === \"claude-code\") {\n return claudeCounter;\n }\n\n return codexCounter;\n}\n\nfunction approximateTokenCount(text: string): number {\n if (text.length === 0) {\n return 0;\n }\n\n return Math.max(1, Math.ceil(text.length / 4));\n}\n\nfunction countTokensWithFallback(text: string, counter: TokenCounter): TokenCountResult {\n try {\n return {\n tokens: counter.countTokens(text),\n usedFallback: false,\n };\n } catch {\n return {\n tokens: approximateTokenCount(text),\n usedFallback: true,\n };\n }\n}\n\nfunction chooseConservativeComplexity(\n declaredComplexity: TaskComplexity,\n analyzedComplexity: TaskComplexity,\n): TaskComplexity {\n return COMPLEXITY_ORDER[declaredComplexity] >= COMPLEXITY_ORDER[analyzedComplexity]\n ? declaredComplexity\n : analyzedComplexity;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n if (value < min) {\n return min;\n }\n\n if (value > max) {\n return max;\n }\n\n return value;\n}\n\nfunction resolveTargetFilePath(targetFile: string): string {\n return isAbsolute(targetFile) ? targetFile : resolve(process.cwd(), targetFile);\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n const serialized = JSON.stringify(value);\n return serialized ?? \"null\";\n } catch {\n return \"[unserializable]\";\n }\n}\n\nasync function readContextFile(\n targetFile: string,\n counter: TokenCounter,\n): Promise<ContextFileResult> {\n const resolvedPath = resolveTargetFilePath(targetFile);\n\n try {\n const content = await readFile(resolvedPath, \"utf8\");\n const counted = countTokensWithFallback(content, counter);\n\n return {\n ...counted,\n missing: false,\n };\n } catch {\n return {\n tokens: 0,\n usedFallback: false,\n missing: true,\n };\n }\n}\n\nexport async function estimateTokens(\n task: Task,\n provider: AgentProviderId,\n): Promise<TokenEstimate> {\n const counter = getTokenCounter(provider);\n const uniqueTargetFiles = [...new Set(task.targetFiles)];\n\n const fileResults = await Promise.all(\n uniqueTargetFiles.map((targetFile) => readContextFile(targetFile, counter)),\n );\n\n const repoStructureSeed = uniqueTargetFiles.join(\"\\n\");\n const repoStructureCount = countTokensWithFallback(repoStructureSeed, counter);\n\n const contextTokens =\n repoStructureCount.tokens + fileResults.reduce((sum, result) => sum + result.tokens, 0);\n\n const promptSeed = [\n `Task ID: ${task.id}`,\n `Title: ${task.title}`,\n `Source: ${task.source}`,\n `Priority: ${task.priority}`,\n `Description:\\n${task.description}`,\n `Target Files:\\n${uniqueTargetFiles.join(\"\\n\") || \"(none)\"}`,\n `Metadata: ${safeStringify(task.metadata)}`,\n ].join(\"\\n\\n\");\n\n const promptContentCount = countTokensWithFallback(promptSeed, counter);\n const promptTokens = counter.invocationOverhead + promptContentCount.tokens;\n\n const analyzedComplexity = analyzeTaskComplexity(task);\n const effectiveComplexity = chooseConservativeComplexity(task.complexity, analyzedComplexity);\n const expectedOutputTokens = Math.ceil(\n contextTokens * COMPLEXITY_MULTIPLIERS[effectiveComplexity],\n );\n\n const rawTotalTokens = contextTokens + promptTokens + expectedOutputTokens;\n const totalEstimatedTokens = Math.ceil(rawTotalTokens * ESTIMATION_PADDING_MULTIPLIER);\n\n const usedFallback =\n repoStructureCount.usedFallback ||\n promptContentCount.usedFallback ||\n fileResults.some((result) => result.usedFallback);\n\n const missingFileCount = fileResults.filter((result) => result.missing).length;\n\n let confidence = COMPLEXITY_CONFIDENCE[effectiveComplexity];\n if (usedFallback) {\n confidence = Math.min(confidence, FALLBACK_CONFIDENCE);\n }\n\n if (missingFileCount > 0) {\n confidence -= Math.min(0.25, missingFileCount * 0.05);\n }\n\n if (uniqueTargetFiles.length === 0) {\n confidence -= 0.1;\n }\n\n if (task.complexity !== analyzedComplexity) {\n confidence -= 0.05;\n }\n\n const feasible = totalEstimatedTokens <= counter.maxContextTokens;\n\n return {\n taskId: task.id,\n providerId: provider,\n contextTokens,\n promptTokens,\n expectedOutputTokens,\n totalEstimatedTokens,\n confidence: clamp(confidence, 0.1, 0.95),\n feasible,\n };\n}\n","import { type Tiktoken, get_encoding } from \"tiktoken\";\n\nconst CLAUDE_ENCODING = \"cl100k_base\";\nconst CLAUDE_INVOCATION_OVERHEAD = 1_500;\nconst CLAUDE_MAX_CONTEXT_TOKENS = 200_000;\n\nlet encoder: Tiktoken | undefined;\n\nfunction getEncoder(): Tiktoken {\n encoder ??= get_encoding(CLAUDE_ENCODING);\n return encoder;\n}\n\nexport class ClaudeTokenCounter {\n readonly invocationOverhead = CLAUDE_INVOCATION_OVERHEAD;\n readonly maxContextTokens = CLAUDE_MAX_CONTEXT_TOKENS;\n readonly encoding = CLAUDE_ENCODING;\n\n countTokens(text: string): number {\n return getEncoder().encode(text).length;\n }\n}\n","import { type Tiktoken, get_encoding } from \"tiktoken\";\n\nconst CODEX_INVOCATION_OVERHEAD = 1_000;\nconst CODEX_MAX_CONTEXT_TOKENS = 200_000;\nconst PRIMARY_ENCODING = \"o200k_base\";\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\ntype SupportedCodexEncoding = typeof PRIMARY_ENCODING | typeof FALLBACK_ENCODING;\n\nlet encoder: Tiktoken | undefined;\nlet selectedEncoding: SupportedCodexEncoding | undefined;\n\nfunction getEncoder(): Tiktoken {\n if (encoder) {\n return encoder;\n }\n\n try {\n encoder = get_encoding(PRIMARY_ENCODING);\n selectedEncoding = PRIMARY_ENCODING;\n } catch {\n encoder = get_encoding(FALLBACK_ENCODING);\n selectedEncoding = FALLBACK_ENCODING;\n }\n\n return encoder;\n}\n\nexport class CodexTokenCounter {\n readonly invocationOverhead = CODEX_INVOCATION_OVERHEAD;\n readonly maxContextTokens = CODEX_MAX_CONTEXT_TOKENS;\n\n get encoding(): SupportedCodexEncoding {\n getEncoder();\n return selectedEncoding ?? FALLBACK_ENCODING;\n }\n\n countTokens(text: string): number {\n return getEncoder().encode(text).length;\n }\n}\n","import type { AgentProviderId, Task, TokenEstimate } from \"./estimator.js\";\n\nconst DEFAULT_RESERVE_PERCENT = 0.1;\nconst MIN_CONFIDENCE_THRESHOLD = 0.5;\nconst TOO_COMPLEX_BUDGET_SHARE = 0.6;\n\ntype DeferredReason = \"budget_exceeded\" | \"low_confidence\" | \"too_complex\";\n\nexport interface ExecutionPlan {\n totalBudget: number;\n selectedTasks: Array<{\n task: Task;\n estimate: TokenEstimate;\n cumulativeBudgetUsed: number;\n }>;\n deferredTasks: Array<{\n task: Task;\n estimate: TokenEstimate;\n reason: DeferredReason;\n }>;\n reserveTokens: number;\n remainingTokens: number;\n}\n\ninterface CandidateTask {\n task: Task;\n estimate: TokenEstimate;\n}\n\nfunction normalizeBudget(budget: number): number {\n if (!Number.isFinite(budget) || budget <= 0) {\n return 0;\n }\n\n return Math.floor(budget);\n}\n\nfunction getFallbackEstimate(task: Task): TokenEstimate {\n return {\n taskId: task.id,\n providerId: \"unknown\" as AgentProviderId,\n contextTokens: 0,\n promptTokens: 0,\n expectedOutputTokens: 0,\n totalEstimatedTokens: Number.MAX_SAFE_INTEGER,\n confidence: 0,\n feasible: false,\n };\n}\n\nfunction classifyDeferredReason(\n task: Task,\n estimate: TokenEstimate,\n effectiveBudget: number,\n): DeferredReason | undefined {\n if (!estimate.feasible) {\n return \"budget_exceeded\";\n }\n\n if (estimate.confidence < MIN_CONFIDENCE_THRESHOLD) {\n return \"low_confidence\";\n }\n\n if (\n task.complexity === \"complex\" &&\n effectiveBudget > 0 &&\n estimate.totalEstimatedTokens > effectiveBudget * TOO_COMPLEX_BUDGET_SHARE\n ) {\n return \"too_complex\";\n }\n\n return undefined;\n}\n\nfunction scoreByPriorityPerToken(task: Task, estimate: TokenEstimate): number {\n if (estimate.totalEstimatedTokens <= 0) {\n return task.priority;\n }\n\n return task.priority / estimate.totalEstimatedTokens;\n}\n\nexport function buildExecutionPlan(\n tasks: Task[],\n estimates: Map<string, TokenEstimate>,\n budget: number,\n): ExecutionPlan {\n const totalBudget = normalizeBudget(budget);\n const reserveTokens = Math.floor(totalBudget * DEFAULT_RESERVE_PERCENT);\n const effectiveBudget = Math.max(0, totalBudget - reserveTokens);\n\n const deferredTasks: ExecutionPlan[\"deferredTasks\"] = [];\n const candidates: CandidateTask[] = [];\n\n for (const task of tasks) {\n const estimate = estimates.get(task.id) ?? getFallbackEstimate(task);\n const reason = classifyDeferredReason(task, estimate, effectiveBudget);\n\n if (reason) {\n deferredTasks.push({ task, estimate, reason });\n continue;\n }\n\n candidates.push({ task, estimate });\n }\n\n candidates.sort((left, right) => {\n const ratioDifference =\n scoreByPriorityPerToken(right.task, right.estimate) -\n scoreByPriorityPerToken(left.task, left.estimate);\n\n if (ratioDifference !== 0) {\n return ratioDifference;\n }\n\n const priorityDifference = right.task.priority - left.task.priority;\n if (priorityDifference !== 0) {\n return priorityDifference;\n }\n\n return left.estimate.totalEstimatedTokens - right.estimate.totalEstimatedTokens;\n });\n\n const selectedTasks: ExecutionPlan[\"selectedTasks\"] = [];\n let budgetUsed = 0;\n\n for (const candidate of candidates) {\n const nextBudgetUsed = budgetUsed + candidate.estimate.totalEstimatedTokens;\n\n if (nextBudgetUsed <= effectiveBudget) {\n budgetUsed = nextBudgetUsed;\n selectedTasks.push({\n task: candidate.task,\n estimate: candidate.estimate,\n cumulativeBudgetUsed: budgetUsed,\n });\n continue;\n }\n\n deferredTasks.push({\n task: candidate.task,\n estimate: candidate.estimate,\n reason: \"budget_exceeded\",\n });\n }\n\n return {\n totalBudget,\n selectedTasks,\n deferredTasks,\n reserveTokens,\n remainingTokens: Math.max(0, effectiveBudget - budgetUsed),\n };\n}\n"],"mappings":";AAEA,IAAM,sBAAkD;AAAA,EACtD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,QAAQ;AACV;AAEA,IAAM,0BAAsD;AAAA,EAC1D,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,QAAQ;AACV;AAEA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,kBAAkB,OAAoC;AAC7D,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG;AACrE,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,WAAW,KAAK;AACtC,QAAI,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC1C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAuD;AACtF,aAAW,OAAO,oBAAoB;AACpC,UAAM,cAAc,kBAAkB,SAAS,GAAG,CAAC;AACnD,QAAI,gBAAgB,QAAW;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UAAU,SAAS;AACzB,MAAI,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG;AACrE,UAAM,gBAAgB;AACtB,eAAW,OAAO,oBAAoB;AACpC,YAAM,cAAc,kBAAkB,cAAc,GAAG,CAAC;AACxD,UAAI,gBAAgB,QAAW;AAC7B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,mBAAmB,MAAoB;AACrD,QAAM,mBAAmB,wBAAwB,KAAK,QAAQ;AAC9D,MAAI,qBAAqB,QAAW;AAClC,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,CAAC;AAAA,EACjD;AAEA,QAAM,iBAAiB,oBAAoB,KAAK,MAAM,KAAK,oBAAoB;AAC/E,QAAM,iBAAiB,KAAK,IAAI,KAAK,YAAY,QAAQ,CAAC,IAAI;AAE9D,SAAO,KAAK,IAAI,gBAAgB,cAAc;AAChD;AAEO,SAAS,sBAAsB,MAA4B;AAChE,QAAM,YAAY,KAAK,YAAY;AACnC,QAAM,aAAa,mBAAmB,IAAI;AAE1C,QAAM,YAAY,aAAa,IAAI,IAAI,aAAa,IAAI,IAAI,aAAa,IAAI,IAAI;AACjF,QAAM,WAAW,cAAc,KAAK,IAAI,cAAc,KAAK,IAAI,cAAc,MAAM,IAAI;AACvF,QAAM,cAAc,wBAAwB,KAAK,MAAM,KAAK,wBAAwB;AAEpF,QAAM,aAAa,YAAY,WAAW;AAE1C,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;ACzGA,SAAS,gBAAgB;AACzB,SAAS,YAAY,eAAe;;;ACDpC,SAAwB,oBAAoB;AAE5C,IAAM,kBAAkB;AACxB,IAAM,6BAA6B;AACnC,IAAM,4BAA4B;AAElC,IAAI;AAEJ,SAAS,aAAuB;AAC9B,cAAY,aAAa,eAAe;AACxC,SAAO;AACT;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,WAAW;AAAA,EAEpB,YAAY,MAAsB;AAChC,WAAO,WAAW,EAAE,OAAO,IAAI,EAAE;AAAA,EACnC;AACF;;;ACrBA,SAAwB,gBAAAA,qBAAoB;AAE5C,IAAM,4BAA4B;AAClC,IAAM,2BAA2B;AACjC,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAI1B,IAAIC;AACJ,IAAI;AAEJ,SAASC,cAAuB;AAC9B,MAAID,UAAS;AACX,WAAOA;AAAA,EACT;AAEA,MAAI;AACF,IAAAA,WAAUD,cAAa,gBAAgB;AACvC,uBAAmB;AAAA,EACrB,QAAQ;AACN,IAAAC,WAAUD,cAAa,iBAAiB;AACxC,uBAAmB;AAAA,EACrB;AAEA,SAAOC;AACT;AAEO,IAAM,oBAAN,MAAwB;AAAA,EACpB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EAE5B,IAAI,WAAmC;AACrC,IAAAC,YAAW;AACX,WAAO,oBAAoB;AAAA,EAC7B;AAAA,EAEA,YAAY,MAAsB;AAChC,WAAOA,YAAW,EAAE,OAAO,IAAI,EAAE;AAAA,EACnC;AACF;;;AFoBA,IAAM,gCAAgC;AACtC,IAAM,sBAAsB;AAE5B,IAAM,yBAAyD;AAAA,EAC7D,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AACX;AAEA,IAAM,wBAAwD;AAAA,EAC5D,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AACX;AAEA,IAAM,mBAAmD;AAAA,EACvD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AACX;AAEA,IAAM,gBAAgB,IAAI,mBAAmB;AAC7C,IAAM,eAAe,IAAI,kBAAkB;AAE3C,SAAS,gBAAgB,UAAyC;AAChE,MAAI,aAAa,eAAe;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAAsB;AACnD,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,SAAS,CAAC,CAAC;AAC/C;AAEA,SAAS,wBAAwB,MAAc,SAAyC;AACtF,MAAI;AACF,WAAO;AAAA,MACL,QAAQ,QAAQ,YAAY,IAAI;AAAA,MAChC,cAAc;AAAA,IAChB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,QAAQ,sBAAsB,IAAI;AAAA,MAClC,cAAc;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,6BACP,oBACA,oBACgB;AAChB,SAAO,iBAAiB,kBAAkB,KAAK,iBAAiB,kBAAkB,IAC9E,qBACA;AACN;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D,MAAI,QAAQ,KAAK;AACf,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,KAAK;AACf,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,YAA4B;AACzD,SAAO,WAAW,UAAU,IAAI,aAAa,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAChF;AAEA,SAAS,cAAc,OAAwB;AAC7C,MAAI;AACF,UAAM,aAAa,KAAK,UAAU,KAAK;AACvC,WAAO,cAAc;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,gBACb,YACA,SAC4B;AAC5B,QAAM,eAAe,sBAAsB,UAAU;AAErD,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,MAAM;AACnD,UAAM,UAAU,wBAAwB,SAAS,OAAO;AAExD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS;AAAA,IACX;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,eAAsB,eACpB,MACA,UACwB;AACxB,QAAM,UAAU,gBAAgB,QAAQ;AACxC,QAAM,oBAAoB,CAAC,GAAG,IAAI,IAAI,KAAK,WAAW,CAAC;AAEvD,QAAM,cAAc,MAAM,QAAQ;AAAA,IAChC,kBAAkB,IAAI,CAAC,eAAe,gBAAgB,YAAY,OAAO,CAAC;AAAA,EAC5E;AAEA,QAAM,oBAAoB,kBAAkB,KAAK,IAAI;AACrD,QAAM,qBAAqB,wBAAwB,mBAAmB,OAAO;AAE7E,QAAM,gBACJ,mBAAmB,SAAS,YAAY,OAAO,CAAC,KAAK,WAAW,MAAM,OAAO,QAAQ,CAAC;AAExF,QAAM,aAAa;AAAA,IACjB,YAAY,KAAK,EAAE;AAAA,IACnB,UAAU,KAAK,KAAK;AAAA,IACpB,WAAW,KAAK,MAAM;AAAA,IACtB,aAAa,KAAK,QAAQ;AAAA,IAC1B;AAAA,EAAiB,KAAK,WAAW;AAAA,IACjC;AAAA,EAAkB,kBAAkB,KAAK,IAAI,KAAK,QAAQ;AAAA,IAC1D,aAAa,cAAc,KAAK,QAAQ,CAAC;AAAA,EAC3C,EAAE,KAAK,MAAM;AAEb,QAAM,qBAAqB,wBAAwB,YAAY,OAAO;AACtE,QAAM,eAAe,QAAQ,qBAAqB,mBAAmB;AAErE,QAAM,qBAAqB,sBAAsB,IAAI;AACrD,QAAM,sBAAsB,6BAA6B,KAAK,YAAY,kBAAkB;AAC5F,QAAM,uBAAuB,KAAK;AAAA,IAChC,gBAAgB,uBAAuB,mBAAmB;AAAA,EAC5D;AAEA,QAAM,iBAAiB,gBAAgB,eAAe;AACtD,QAAM,uBAAuB,KAAK,KAAK,iBAAiB,6BAA6B;AAErF,QAAM,eACJ,mBAAmB,gBACnB,mBAAmB,gBACnB,YAAY,KAAK,CAAC,WAAW,OAAO,YAAY;AAElD,QAAM,mBAAmB,YAAY,OAAO,CAAC,WAAW,OAAO,OAAO,EAAE;AAExE,MAAI,aAAa,sBAAsB,mBAAmB;AAC1D,MAAI,cAAc;AAChB,iBAAa,KAAK,IAAI,YAAY,mBAAmB;AAAA,EACvD;AAEA,MAAI,mBAAmB,GAAG;AACxB,kBAAc,KAAK,IAAI,MAAM,mBAAmB,IAAI;AAAA,EACtD;AAEA,MAAI,kBAAkB,WAAW,GAAG;AAClC,kBAAc;AAAA,EAChB;AAEA,MAAI,KAAK,eAAe,oBAAoB;AAC1C,kBAAc;AAAA,EAChB;AAEA,QAAM,WAAW,wBAAwB,QAAQ;AAEjD,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,MAAM,YAAY,KAAK,IAAI;AAAA,IACvC;AAAA,EACF;AACF;;;AGvPA,IAAM,0BAA0B;AAChC,IAAM,2BAA2B;AACjC,IAAM,2BAA2B;AAyBjC,SAAS,gBAAgB,QAAwB;AAC/C,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,MAAM,MAAM;AAC1B;AAEA,SAAS,oBAAoB,MAA2B;AACtD,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,cAAc;AAAA,IACd,sBAAsB;AAAA,IACtB,sBAAsB,OAAO;AAAA,IAC7B,YAAY;AAAA,IACZ,UAAU;AAAA,EACZ;AACF;AAEA,SAAS,uBACP,MACA,UACA,iBAC4B;AAC5B,MAAI,CAAC,SAAS,UAAU;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,aAAa,0BAA0B;AAClD,WAAO;AAAA,EACT;AAEA,MACE,KAAK,eAAe,aACpB,kBAAkB,KAClB,SAAS,uBAAuB,kBAAkB,0BAClD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,wBAAwB,MAAY,UAAiC;AAC5E,MAAI,SAAS,wBAAwB,GAAG;AACtC,WAAO,KAAK;AAAA,EACd;AAEA,SAAO,KAAK,WAAW,SAAS;AAClC;AAEO,SAAS,mBACd,OACA,WACA,QACe;AACf,QAAM,cAAc,gBAAgB,MAAM;AAC1C,QAAM,gBAAgB,KAAK,MAAM,cAAc,uBAAuB;AACtE,QAAM,kBAAkB,KAAK,IAAI,GAAG,cAAc,aAAa;AAE/D,QAAM,gBAAgD,CAAC;AACvD,QAAM,aAA8B,CAAC;AAErC,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,UAAU,IAAI,KAAK,EAAE,KAAK,oBAAoB,IAAI;AACnE,UAAM,SAAS,uBAAuB,MAAM,UAAU,eAAe;AAErE,QAAI,QAAQ;AACV,oBAAc,KAAK,EAAE,MAAM,UAAU,OAAO,CAAC;AAC7C;AAAA,IACF;AAEA,eAAW,KAAK,EAAE,MAAM,SAAS,CAAC;AAAA,EACpC;AAEA,aAAW,KAAK,CAAC,MAAM,UAAU;AAC/B,UAAM,kBACJ,wBAAwB,MAAM,MAAM,MAAM,QAAQ,IAClD,wBAAwB,KAAK,MAAM,KAAK,QAAQ;AAElD,QAAI,oBAAoB,GAAG;AACzB,aAAO;AAAA,IACT;AAEA,UAAM,qBAAqB,MAAM,KAAK,WAAW,KAAK,KAAK;AAC3D,QAAI,uBAAuB,GAAG;AAC5B,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,SAAS,uBAAuB,MAAM,SAAS;AAAA,EAC7D,CAAC;AAED,QAAM,gBAAgD,CAAC;AACvD,MAAI,aAAa;AAEjB,aAAW,aAAa,YAAY;AAClC,UAAM,iBAAiB,aAAa,UAAU,SAAS;AAEvD,QAAI,kBAAkB,iBAAiB;AACrC,mBAAa;AACb,oBAAc,KAAK;AAAA,QACjB,MAAM,UAAU;AAAA,QAChB,UAAU,UAAU;AAAA,QACpB,sBAAsB;AAAA,MACxB,CAAC;AACD;AAAA,IACF;AAEA,kBAAc,KAAK;AAAA,MACjB,MAAM,UAAU;AAAA,MAChB,UAAU,UAAU;AAAA,MACpB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,KAAK,IAAI,GAAG,kBAAkB,UAAU;AAAA,EAC3D;AACF;","names":["get_encoding","encoder","getEncoder"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open330/oac-budget",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.4",
|
|
4
4
|
"description": "Token estimation and execution planning for OAC",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"tiktoken": "^1.0.18",
|
|
32
|
-
"@open330/oac-core": "2026.3.
|
|
32
|
+
"@open330/oac-core": "2026.3.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"tsup": "^8.3.6",
|