mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 02:17:11 +00:00
chore: update to v3.4.6 with reasoning enforcer and GLMT improvements
This commit is contained in:
@@ -2,6 +2,24 @@
|
||||
|
||||
Format: [Keep a Changelog](https://keepachangelog.com/)
|
||||
|
||||
## [3.4.6] - 2025-11-12
|
||||
|
||||
### Added
|
||||
- GLMT ReasoningEnforcer: Prompt injection + API params hybrid (4 effort levels, always enabled)
|
||||
|
||||
### Changed
|
||||
- Added GLMT production warnings (NOT PRODUCTION READY)
|
||||
- Streamlined CLAUDE.md (-337 lines)
|
||||
- Simplified GLMT controls: 4 mechanisms → 3 automatic
|
||||
- Locale + reasoning enforcement now always enabled
|
||||
|
||||
### Removed
|
||||
- GLMT Budget Calculator mechanism (consolidated into automatic controls)
|
||||
- Deprecated GLMT environment variables (`CCS_GLMT_FORCE_ENGLISH`, `CCS_GLMT_THINKING_BUDGET`, `CCS_GLMT_STREAMING`)
|
||||
- Outdated test scenarios for removed environment variables
|
||||
|
||||
---
|
||||
|
||||
## [3.4.5] - 2025-11-11
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -32,14 +32,21 @@ CLI wrapper for instant switching between multiple Claude accounts and alternati
|
||||
- `bin/glmt/glmt-proxy.js`: HTTP proxy server with streaming + auto-fallback
|
||||
- `bin/glmt/glmt-transformer.js`: Format conversion + delta handling + tool transformation
|
||||
- `bin/glmt/locale-enforcer.js`: Enforces English output
|
||||
- `bin/glmt/reasoning-enforcer.js`: Injects explicit reasoning instructions (hybrid approach)
|
||||
- `bin/glmt/sse-parser.js`: SSE stream parser
|
||||
- `bin/glmt/delta-accumulator.js`: State tracking for streaming + tool calls
|
||||
- `tests/unit/glmt/glmt-transformer.test.js`: Unit tests (35 tests passing)
|
||||
- `tests/unit/glmt/reasoning-enforcer.test.js`: ReasoningEnforcer unit tests (15 tests passing)
|
||||
|
||||
**Thinking control mechanisms**:
|
||||
- Keywords: `think`, `think hard`, `think harder`, `ultrathink`
|
||||
- Tags: `<Thinking:On|Off>`, `<Effort:Low|Medium|High>`
|
||||
- Precedence: CLI parameter > message tags > keywords
|
||||
**Reasoning control mechanisms (hybrid approach)**:
|
||||
- **Keywords**: `think`, `think hard`, `think harder`, `ultrathink`
|
||||
- **Tags**: `<Thinking:On|Off>`, `<Effort:Low|Medium|High>`
|
||||
- **Precedence**: CLI parameter > message tags > keywords
|
||||
- **Hybrid mode**: Uses BOTH API parameters (`reasoning: true`) AND prompt injection
|
||||
- API params: Native Z.AI support (deterministic, zero overhead)
|
||||
- Prompt injection: Explicit format instructions using `<reasoning_content>` tags
|
||||
- ReasoningEnforcer has 4 effort-aware prompts (low/medium/high/max)
|
||||
- **Enabled by default** for all GLMT usage (always active)
|
||||
|
||||
**Security limits** (DoS protection):
|
||||
- SSE buffer: 1MB max
|
||||
|
||||
@@ -8,6 +8,7 @@ const os = require('os');
|
||||
const SSEParser = require('./sse-parser');
|
||||
const DeltaAccumulator = require('./delta-accumulator');
|
||||
const LocaleEnforcer = require('./locale-enforcer');
|
||||
const ReasoningEnforcer = require('./reasoning-enforcer');
|
||||
|
||||
/**
|
||||
* GlmtTransformer - Convert between Anthropic and OpenAI formats with thinking and tool support
|
||||
@@ -54,6 +55,11 @@ class GlmtTransformer {
|
||||
|
||||
// Initialize locale enforcer (always enforce English)
|
||||
this.localeEnforcer = new LocaleEnforcer();
|
||||
|
||||
// Initialize reasoning enforcer (enabled by default for all GLMT usage)
|
||||
this.reasoningEnforcer = new ReasoningEnforcer({
|
||||
enabled: config.explicitReasoning ?? true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,10 +110,16 @@ class GlmtTransformer {
|
||||
anthropicRequest.messages || []
|
||||
);
|
||||
|
||||
// 4.5. Inject reasoning instruction (if enabled or thinking requested)
|
||||
const messagesWithReasoning = this.reasoningEnforcer.injectInstruction(
|
||||
messagesWithLocale,
|
||||
thinkingConfig
|
||||
);
|
||||
|
||||
// 5. Convert to OpenAI format
|
||||
const openaiRequest = {
|
||||
model: glmModel,
|
||||
messages: this._sanitizeMessages(messagesWithLocale),
|
||||
messages: this._sanitizeMessages(messagesWithReasoning),
|
||||
max_tokens: this._getMaxTokens(glmModel),
|
||||
stream: anthropicRequest.stream ?? false
|
||||
};
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ReasoningEnforcer - Inject explicit reasoning instructions into prompts
|
||||
*
|
||||
* Purpose: Force GLM models to use structured reasoning output format (<reasoning_content>)
|
||||
* This complements API parameters (reasoning: true) with explicit prompt instructions.
|
||||
*
|
||||
* Usage:
|
||||
* const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
* const modifiedMessages = enforcer.injectInstruction(messages, thinkingConfig);
|
||||
*
|
||||
* Strategy:
|
||||
* 1. If system prompt exists: Prepend reasoning instruction
|
||||
* 2. If no system prompt: Prepend to first user message
|
||||
* 3. Select prompt template based on effort level (low/medium/high/max)
|
||||
* 4. Preserve message structure (string vs array content)
|
||||
*/
|
||||
|
||||
class ReasoningEnforcer {
|
||||
constructor(options = {}) {
|
||||
this.enabled = options.enabled ?? false; // Opt-in by default
|
||||
this.prompts = options.prompts || this._getDefaultPrompts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject reasoning instruction into messages
|
||||
* @param {Array} messages - Messages array to modify
|
||||
* @param {Object} thinkingConfig - { thinking: boolean, effort: string }
|
||||
* @returns {Array} Modified messages array
|
||||
*/
|
||||
injectInstruction(messages, thinkingConfig = {}) {
|
||||
// Only inject if enabled or thinking explicitly requested
|
||||
if (!this.enabled && !thinkingConfig.thinking) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
// Clone messages to avoid mutation
|
||||
const modifiedMessages = JSON.parse(JSON.stringify(messages));
|
||||
|
||||
// Select prompt based on effort level
|
||||
const prompt = this._selectPrompt(thinkingConfig.effort || 'medium');
|
||||
|
||||
// Strategy 1: Inject into system prompt (preferred)
|
||||
const systemIndex = modifiedMessages.findIndex(m => m.role === 'system');
|
||||
if (systemIndex >= 0) {
|
||||
const systemMsg = modifiedMessages[systemIndex];
|
||||
|
||||
if (typeof systemMsg.content === 'string') {
|
||||
systemMsg.content = `${prompt}\n\n${systemMsg.content}`;
|
||||
} else if (Array.isArray(systemMsg.content)) {
|
||||
systemMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: prompt
|
||||
});
|
||||
}
|
||||
|
||||
return modifiedMessages;
|
||||
}
|
||||
|
||||
// Strategy 2: Prepend to first user message
|
||||
const userIndex = modifiedMessages.findIndex(m => m.role === 'user');
|
||||
if (userIndex >= 0) {
|
||||
const userMsg = modifiedMessages[userIndex];
|
||||
|
||||
if (typeof userMsg.content === 'string') {
|
||||
userMsg.content = `${prompt}\n\n${userMsg.content}`;
|
||||
} else if (Array.isArray(userMsg.content)) {
|
||||
userMsg.content.unshift({
|
||||
type: 'text',
|
||||
text: prompt
|
||||
});
|
||||
}
|
||||
|
||||
return modifiedMessages;
|
||||
}
|
||||
|
||||
// No system or user messages found (edge case)
|
||||
return modifiedMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select prompt template based on effort level
|
||||
* @param {string} effort - 'low', 'medium', 'high', or 'max'
|
||||
* @returns {string} Prompt template
|
||||
* @private
|
||||
*/
|
||||
_selectPrompt(effort) {
|
||||
const normalizedEffort = effort.toLowerCase();
|
||||
return this.prompts[normalizedEffort] || this.prompts.medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default prompt templates
|
||||
* @returns {Object} Map of effort levels to prompts
|
||||
* @private
|
||||
*/
|
||||
_getDefaultPrompts() {
|
||||
return {
|
||||
low: `You are an expert reasoning model using GLM-4.6 architecture.
|
||||
|
||||
CRITICAL: Before answering, write 2-3 sentences of reasoning in <reasoning_content> tags.
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<reasoning_content>
|
||||
(Brief analysis: what is the problem? what's the approach?)
|
||||
</reasoning_content>
|
||||
|
||||
(Write your final answer here)`,
|
||||
|
||||
medium: `You are an expert reasoning model using GLM-4.6 architecture.
|
||||
|
||||
CRITICAL REQUIREMENTS:
|
||||
1. Always think step-by-step before answering
|
||||
2. Write your reasoning process explicitly in <reasoning_content> tags
|
||||
3. Never skip your chain of thought, even for simple problems
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<reasoning_content>
|
||||
(Write your detailed thinking here: analyze the problem, explore approaches,
|
||||
evaluate trade-offs, and arrive at a conclusion)
|
||||
</reasoning_content>
|
||||
|
||||
(Write your final answer here based on your reasoning above)`,
|
||||
|
||||
high: `You are an expert reasoning model using GLM-4.6 architecture.
|
||||
|
||||
CRITICAL REQUIREMENTS:
|
||||
1. Think deeply and systematically before answering
|
||||
2. Write comprehensive reasoning in <reasoning_content> tags
|
||||
3. Explore multiple approaches and evaluate trade-offs
|
||||
4. Show all steps in your problem-solving process
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<reasoning_content>
|
||||
(Write exhaustive analysis here:
|
||||
- Problem decomposition
|
||||
- Multiple approach exploration
|
||||
- Trade-off analysis for each approach
|
||||
- Edge case consideration
|
||||
- Final conclusion with justification)
|
||||
</reasoning_content>
|
||||
|
||||
(Write your final answer here based on your systematic reasoning above)`,
|
||||
|
||||
max: `You are an expert reasoning model using GLM-4.6 architecture.
|
||||
|
||||
CRITICAL REQUIREMENTS:
|
||||
1. Think exhaustively from first principles
|
||||
2. Write extremely detailed reasoning in <reasoning_content> tags
|
||||
3. Analyze ALL possible angles, approaches, and edge cases
|
||||
4. Challenge your own assumptions and explore alternatives
|
||||
5. Provide rigorous justification for every claim
|
||||
|
||||
OUTPUT FORMAT:
|
||||
<reasoning_content>
|
||||
(Write comprehensive analysis here:
|
||||
- First principles breakdown
|
||||
- Exhaustive approach enumeration
|
||||
- Comparative analysis of all approaches
|
||||
- Edge case and failure mode analysis
|
||||
- Assumption validation
|
||||
- Counter-argument consideration
|
||||
- Final conclusion with rigorous justification)
|
||||
</reasoning_content>
|
||||
|
||||
(Write your final answer here based on your exhaustive reasoning above)`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReasoningEnforcer;
|
||||
@@ -31,7 +31,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (irm | iex)
|
||||
# For git installations, VERSION file is read if available
|
||||
$CcsVersion = "3.4.5"
|
||||
$CcsVersion = "3.4.6"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if ($ScriptDir) {
|
||||
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
# IMPORTANT: Update this version when releasing new versions!
|
||||
# This hardcoded version is used for standalone installations (curl | bash)
|
||||
# For git installations, VERSION file is read if available
|
||||
CCS_VERSION="3.4.5"
|
||||
CCS_VERSION="3.4.6"
|
||||
|
||||
# Try to read VERSION file for git installations
|
||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Version (updated by scripts/bump-version.sh)
|
||||
CCS_VERSION="3.4.5"
|
||||
CCS_VERSION="3.4.6"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
||||
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ param(
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Version (updated by scripts/bump-version.sh)
|
||||
$CcsVersion = "3.4.5"
|
||||
$CcsVersion = "3.4.6"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
||||
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kaitranntt/ccs",
|
||||
"version": "3.4.5",
|
||||
"version": "3.4.6",
|
||||
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ReasoningEnforcer Unit Tests
|
||||
*
|
||||
* Test scenarios:
|
||||
* 1. Opt-in behavior (enabled vs disabled)
|
||||
* 2. System message injection
|
||||
* 3. User message fallback
|
||||
* 4. Effort level selection (low/medium/high/max)
|
||||
* 5. Message structure handling (string vs array)
|
||||
* 6. Edge cases (empty messages, no system/user)
|
||||
*/
|
||||
|
||||
const ReasoningEnforcer = require('../../../bin/glmt/reasoning-enforcer');
|
||||
|
||||
/**
|
||||
* Simple test runner (no external dependencies)
|
||||
*/
|
||||
class TestRunner {
|
||||
constructor() {
|
||||
this.tests = [];
|
||||
this.passed = 0;
|
||||
this.failed = 0;
|
||||
}
|
||||
|
||||
test(name, fn) {
|
||||
this.tests.push({ name, fn });
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log('\n=== ReasoningEnforcer Tests ===\n');
|
||||
|
||||
for (const { name, fn } of this.tests) {
|
||||
try {
|
||||
await fn();
|
||||
console.log(`[OK] ${name}`);
|
||||
this.passed++;
|
||||
} catch (error) {
|
||||
console.error(`[X] ${name}`);
|
||||
console.error(` Error: ${error.message}`);
|
||||
this.failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=== Results ===`);
|
||||
console.log(`Passed: ${this.passed}/${this.tests.length}`);
|
||||
console.log(`Failed: ${this.failed}/${this.tests.length}`);
|
||||
|
||||
return this.failed === 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple assertion helpers
|
||||
*/
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(
|
||||
`${message || 'Assertion failed'}\n` +
|
||||
` Expected: ${JSON.stringify(expected)}\n` +
|
||||
` Actual: ${JSON.stringify(actual)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertTrue(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message || 'Expected condition to be true');
|
||||
}
|
||||
}
|
||||
|
||||
function assertIncludes(haystack, needle, message) {
|
||||
if (!haystack.includes(needle)) {
|
||||
throw new Error(
|
||||
`${message || 'String does not include expected substring'}\n` +
|
||||
` Expected substring: ${needle}\n` +
|
||||
` Actual string: ${haystack.substring(0, 100)}...`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertDeepEqual(actual, expected, message) {
|
||||
const actualStr = JSON.stringify(actual);
|
||||
const expectedStr = JSON.stringify(expected);
|
||||
if (actualStr !== expectedStr) {
|
||||
throw new Error(
|
||||
`${message || 'Deep equality assertion failed'}\n` +
|
||||
` Expected: ${expectedStr}\n` +
|
||||
` Actual: ${actualStr}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create test runner
|
||||
const runner = new TestRunner();
|
||||
|
||||
// Test 1: Opt-in behavior - disabled
|
||||
runner.test('should NOT inject when disabled and thinking=false', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: false });
|
||||
const messages = [
|
||||
{ role: 'user', content: 'What is 2+2?' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: false });
|
||||
|
||||
assertEqual(result.length, 1);
|
||||
assertEqual(result[0].content, 'What is 2+2?');
|
||||
});
|
||||
|
||||
// Test 2: Opt-in behavior - enabled
|
||||
runner.test('should inject when enabled=true', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [
|
||||
{ role: 'user', content: 'What is 2+2?' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: false });
|
||||
|
||||
assertIncludes(result[0].content, 'CRITICAL');
|
||||
assertIncludes(result[0].content, '<reasoning_content>');
|
||||
});
|
||||
|
||||
// Test 3: Opt-in behavior - thinking=true
|
||||
runner.test('should inject when thinking=true (even if enabled=false)', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: false });
|
||||
const messages = [
|
||||
{ role: 'user', content: 'What is 2+2?' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertIncludes(result[0].content, 'CRITICAL');
|
||||
});
|
||||
|
||||
// Test 4: System message injection - string content
|
||||
runner.test('should prepend to system message (string content)', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'Calculate 2+2' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
|
||||
|
||||
assertEqual(result.length, 2);
|
||||
assertTrue(result[0].content.startsWith('You are an expert reasoning model'));
|
||||
assertIncludes(result[0].content, 'You are a helpful assistant');
|
||||
assertEqual(result[1].content, 'Calculate 2+2');
|
||||
});
|
||||
|
||||
// Test 5: System message injection - array content
|
||||
runner.test('should prepend to system message (array content)', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
content: [
|
||||
{ type: 'text', text: 'You are a code assistant.' }
|
||||
]
|
||||
},
|
||||
{ role: 'user', content: 'Write a function' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertTrue(Array.isArray(result[0].content));
|
||||
assertEqual(result[0].content[0].type, 'text');
|
||||
assertIncludes(result[0].content[0].text, 'CRITICAL');
|
||||
assertEqual(result[0].content[1].text, 'You are a code assistant.');
|
||||
});
|
||||
|
||||
// Test 6: User message fallback
|
||||
runner.test('should prepend to first user message when no system message', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [
|
||||
{ role: 'user', content: 'Explain quantum computing' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertEqual(result.length, 1);
|
||||
assertIncludes(result[0].content, 'CRITICAL');
|
||||
assertIncludes(result[0].content, 'Explain quantum computing');
|
||||
});
|
||||
|
||||
// Test 7: Effort level - low
|
||||
runner.test('should use low prompt template', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
|
||||
|
||||
assertIncludes(result[0].content.toLowerCase(), 'brief analysis');
|
||||
});
|
||||
|
||||
// Test 8: Effort level - medium
|
||||
runner.test('should use medium prompt template', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
|
||||
|
||||
assertIncludes(result[0].content.toLowerCase(), 'think step-by-step');
|
||||
});
|
||||
|
||||
// Test 9: Effort level - high
|
||||
runner.test('should use high prompt template', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'high' });
|
||||
|
||||
assertIncludes(result[0].content.toLowerCase(), 'think deeply and systematically');
|
||||
});
|
||||
|
||||
// Test 10: Effort level - max
|
||||
runner.test('should use max prompt template', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'max' });
|
||||
|
||||
assertIncludes(result[0].content.toLowerCase(), 'exhaustively from first principles');
|
||||
});
|
||||
|
||||
// Test 11: Default effort level
|
||||
runner.test('should default to medium effort if not specified', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertIncludes(result[0].content, 'think step-by-step');
|
||||
});
|
||||
|
||||
// Test 12: Empty messages array
|
||||
runner.test('should handle empty messages array', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertEqual(result.length, 0);
|
||||
});
|
||||
|
||||
// Test 13: No system or user role
|
||||
runner.test('should handle messages with no system or user role', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const messages = [
|
||||
{ role: 'assistant', content: 'Previous response' }
|
||||
];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true });
|
||||
|
||||
assertEqual(result.length, 1);
|
||||
assertEqual(result[0].content, 'Previous response');
|
||||
});
|
||||
|
||||
// Test 14: Immutability
|
||||
runner.test('should not mutate original messages array', () => {
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true });
|
||||
const originalMessages = [
|
||||
{ role: 'user', content: 'Test prompt' }
|
||||
];
|
||||
const originalCopy = JSON.parse(JSON.stringify(originalMessages));
|
||||
|
||||
enforcer.injectInstruction(originalMessages, { thinking: true });
|
||||
|
||||
assertDeepEqual(originalMessages, originalCopy);
|
||||
});
|
||||
|
||||
// Test 15: Custom prompts
|
||||
runner.test('should handle custom prompts via constructor', () => {
|
||||
const customPrompts = {
|
||||
low: 'Custom low prompt',
|
||||
medium: 'Custom medium prompt',
|
||||
high: 'Custom high prompt',
|
||||
max: 'Custom max prompt'
|
||||
};
|
||||
const enforcer = new ReasoningEnforcer({ enabled: true, prompts: customPrompts });
|
||||
const messages = [{ role: 'user', content: 'Test' }];
|
||||
|
||||
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
|
||||
|
||||
assertIncludes(result[0].content, 'Custom low prompt');
|
||||
});
|
||||
|
||||
// Run all tests
|
||||
runner.run().then(success => {
|
||||
process.exit(success ? 0 : 1);
|
||||
});
|
||||
Reference in New Issue
Block a user