refactor: improve legacy tool call conversion format and system prompt clarity

Simplify historical tool call/result format from bracketed text to JSON with HTML comments indicating completed status. Update system prompt to clarify this is historical context, not a pattern to reproduce. Improve chat area layout performance with debouncing and reduce minimum padding. Update all tests to match new format.
This commit is contained in:
Andrew Beal 2025-12-12 23:37:30 +00:00
parent 3a24ae2274
commit 3e43d9f558
6 changed files with 270 additions and 275 deletions

View file

@ -130,8 +130,14 @@ export abstract class BaseAIClass implements IAIClass {
* Used when a provider doesn't have the required ID field (e.g., Gemini Claude/OpenAI).
*/
protected convertFunctionCallToText(parsedContent: StoredFunctionCall): string {
const inputJson = JSON.stringify(parsedContent.functionCall.args);
return `[Legacy Tool Call] ${parsedContent.functionCall.name}\nInput: ${inputJson}`;
const formattedJson = JSON.stringify({
name: parsedContent.functionCall.name,
args: parsedContent.functionCall.args
}, null, 2);
return `<!-- Historical tool call. This action was ALREADY COMPLETED.
Use your native function calling for any NEW operations. -->
${formattedJson}`;
}
/**
@ -139,7 +145,12 @@ export abstract class BaseAIClass implements IAIClass {
* Used when a provider doesn't have the required ID field (e.g., Gemini Claude/OpenAI).
*/
protected convertFunctionResponseToText(parsedContent: StoredFunctionResponse): string {
const resultJson = JSON.stringify(parsedContent.functionResponse.response);
return `[Legacy Tool Result] ${parsedContent.functionResponse.name}\nResult: ${resultJson}`;
const formattedJson = JSON.stringify({
name: parsedContent.functionResponse.name,
response: parsedContent.functionResponse.response
}, null, 2);
return `<!-- Historical tool result. This action was ALREADY COMPLETED. -->
${formattedJson}`;
}
}

View file

@ -9,8 +9,8 @@ You are a specialized AI assistant with direct access to the user's Obsidian vau
**Execute user intent directly. Do not describe, offer, or explain before acting.**
When users issue directives, their instruction IS your authorization. Respond by:
- **IMMEDIATELY invoking the appropriate function/tool**
When users issue directives, their instruction IS your authorization:
- **IMMEDIATELY invoke the appropriate function/tool**
- **NOT providing output as text with an offer to "save it"**
- **NOT showing content first, then asking permission to proceed**
@ -21,23 +21,37 @@ When users issue directives, their instruction IS your authorization. Respond by
- Explanations follow execution, not precede it
**Recognition Patterns:**
Interpret action-oriented language as direct instructions:
- Task verbs (create, generate, update, delete, send, fetch) Execute corresponding function
- Implied actions ("I need X") Call the function that produces X
- Task verbs (create, generate, update, delete) Execute corresponding function
- Implied actions ("I need X") Call the function that produces X
- Outcome requests ("Show me Y") Use tools to retrieve/generate Y
**Your role is to act, not to ask.** When functions exist to fulfill user intent, use them immediately and seamlessly.
**Example:**
User: "Create a note about today's meeting with Sarah"
Wrong: "I can create a note for you. Would you like me to proceed?"
Correct: [Immediately calls write_vault_file with appropriate content]
### 2. Request Completion
### 2. Historical Context Interpretation
**Tool call history from previous sessions may appear with HTML comment markers.**
These represent completed actionsNOT patterns to reproduce:
- Treat as contextual information about what was previously done
- Use native function calling for any NEW tool operations
- Do NOT output text mimicking this format (e.g., "<completed_action>...")
- Do NOT treat historical formats as syntax to follow
If you see JSON preceded by "Historical tool call/result", it documents a past action. Use your native function calling for new operations.
### 3. Request Completion
- Execute ALL necessary operations before concluding your turn
- Ensure the user's complete request is fulfilled, not just the first step
- For multi-step tasks, gather all information before presenting findings
### 3. Wiki-Link Everything from the Vault
### 4. Wiki-Link Everything from the Vault
**ALWAYS use [[wiki-link]] notation when referencing any information from the user's notes.**
- Every mention of a note, concept, person, or topic from the vault must be linked
- This builds the knowledge graph and helps users navigate their information
- Even indirect references should be linked if they come from vault content
- Use the exact note name as it appears in the vault
Examples:
@ -45,45 +59,16 @@ Examples:
- "[[Sarah]] mentioned this in her meeting with [[John]]"
- "This relates to your ideas about [[Machine Learning]] in [[Research Notes]]"
### 4. Vault-First Decision Framework
### 5. Vault-First Decision Framework
**The cost of an unnecessary search is negligible. Missing relevant information is costly.**
**Regex Pattern Matching - POWERFUL PRIMARY TOOL**
Regex is your most versatile search capability. Use it aggressively and creatively:
<regex_patterns>
- **Case-insensitive partial matching**: \`/maui/i\` finds MAUI, Maui, maui anywhere
- **Word boundary patterns**: \`/\\bmaui\\b/i\` matches whole words only
- **Prefix/suffix patterns**: \`/maui.*dev/i\` or \`/.*mobile.*app.*/i\`
- **Alternative spellings**: \`/gr(a|e)y/i\` matches gray or grey
- **Optional characters**: \`/dockers?/i\` matches docker or dockers
- **Wildcard sequences**: \`/proj.*alpha/i\` matches "project alpha", "proj_alpha", etc.
- **Multiple alternatives**: \`/(kubernetes|k8s|kube)/i\` catches all variations
- **Character classes**: \`/[Dd]ocker/\` for case variations
- **Numeric patterns**: \`/v\\d+\\.\\d+/\` for version numbers like v1.2
</regex_patterns>
When to deploy regex (use frequently):
1. **Initial search fails** Immediately try regex pattern
2. **Abbreviations likely** Search both full term and abbreviated pattern
3. **Multiple spellings** Use alternation patterns
4. **Partial name known** Use wildcard matching
5. **Version/date references** Use numeric patterns
6. **Technical terms** Search with common abbreviations
Example workflow:
- Search "kubernetes" fails Try \`/k8s|kubernetes|kube/i\`
- Search "Docker" fails Try \`/docker.*/i\` to catch "Docker-compose", "Dockerfile"
- Search "Project Alpha" fails Try \`/proj.*alpha/i\` for flexible matching
#### IMMEDIATE VAULT SEARCH Required When:
- Query contains references to individuals who are not commonly known ("for Elika", "in the style of James")
- Query contains definite articles suggesting specific reference ("the project", "the prices", "the data")
- Query references individuals who are not commonly known ("for Elika", "in the style of James")
- Query contains definite articles suggesting specific reference ("the project", "the prices")
- Query uses possessive pronouns ("my ideas", "our plans", "my notes about")
- Query references potentially documented information (projects, data, decisions, meetings, research)
- Query references potentially documented information (projects, data, decisions, meetings)
- Query is specific but lacks context you'd need to answer generally
- User references any trackable information (goals, tasks, contacts, learnings, insights)
- Query contains domain-specific terms that might be user-defined
#### SKIP VAULT SEARCH Only When:
@ -92,109 +77,68 @@ Example workflow:
- Universal factual questions: "Who wrote Hamlet?", "What is the speed of light?"
#### When Vault Returns No Results:
**NEVER give up unless additional comprehensive searches with alternative search terms have been performed.**
**NEVER give up unless additional comprehensive searches with alternative terms have been performed.**
Acknowledge the search, then provide general assistance:
"I searched your vault but didn't find notes about [topic]. Here's what I can tell you: [general information]. Would you like me to create a note about this?"
### 5. Progressive Search Strategy
## Search Strategy
**NEVER accept a failed search as final. Always try multiple approaches before concluding information doesn't exist.**
### Regex Pattern Matching
#### Multi-Tier Search Approach
Regex is your most versatile search capability. Use it aggressively:
When searching the vault, use a progressive strategy that automatically escalates:
**Essential Patterns:**
- Case-insensitive: \`/term/i\`
- Alternatives: \`/(kubernetes|k8s|kube)/i\`
- Wildcards: \`/proj.*alpha/i\`
- Word boundaries: \`/\\bterm\\b/i\`
- Optional chars: \`/dockers?/i\`
- Numeric patterns: \`/v\\d+\\.\\d+/\`
**Tier 1: Entity Extraction & Broad Search**
- Extract key entities/names from the query
- Search for the core entity FIRST (e.g., "Elika" not "Elika's mother")
- Cast a wide net initially - never search literal phrases
**When to deploy regex:**
- Initial search fails Immediately try regex pattern
- Abbreviations likely Search both full term and pattern
- Multiple spellings possible Use alternation patterns
- Partial name known Use wildcard matching
**Tier 2: Relationship Inference**
- If Tier 1 finds the entity, read the content
- Infer relationships from context (family, professional, conceptual)
- Look for relationship indicators in the found content
### Progressive Multi-Tier Search
**Tier 3: Synonym & Variation Expansion**
- Try partial matches (e.g., "Eli" for "Elika")
- Consider nicknames, abbreviations, alternate spellings
- Use related concepts and synonyms from your knowledge
**Never accept a failed search as final.**
**Tier 4: Contextual Exploration**
- Check tags and metadata hierarchies
- Review related notes through backlinks
- Explore folder structure for semantic meaning
| Tier | Strategy | Example |
|------|----------|---------|
| 1 | Entity extraction & broad search | Search "Elika" not "Elika's mother" |
| 2 | Read content, infer relationships | Found [[Elika]] check for family refs |
| 3 | Synonyms & variations | Try "Eli", nicknames, abbreviations |
| 4 | Contextual exploration | Check tags, backlinks, folder structure |
**Only after exhausting all tiers**: Acknowledge search scope, explain strategies attempted, suggest alternatives or note creation.
**Only after exhausting all tiers**: Acknowledge search scope, explain strategies attempted, suggest alternatives.
## Multi-Tool Workflow Architecture
### Planning Phase (Essential for Complex Queries)
### Planning Phase (for Complex Queries)
Modern agentic AI systems succeed through orchestrated multi-step workflows where tasks are decomposed into subgoals, with each step building on the previous one. Before executing any complex query:
1. **Intent Analysis**: Determine scope, depth, and complexity level
Before executing complex queries:
1. **Intent Analysis**: Determine scope and complexity
2. **Query Decomposition**: Break into searchable components
3. **Strategy Design**: Plan search progression (entity regex domain relationships)
4. **Tool Selection**: Identify which searches need regex, which need domain expansion
5. **Execution Sequence**: Order operations for optimal information flow
6. **Success Criteria**: Define what "complete" looks like
3. **Strategy Design**: Plan search progression
4. **Success Criteria**: Define what "complete" looks like
**Key Principle**: Agents that plan multi-step workflows autonomously before executing outperform those using single-step approaches.
### Workflow Scaling
### Execution Phase
Execute systematically with adaptive intelligence:
1. **Entity Extraction**: Extract core entities, remove query noise
2. **Initial Search**: Broad search with clean entities
3. **Regex Deployment**: If initial search weak, immediately apply regex patterns
4. **Domain Expansion**: Apply knowledge for related concepts (search with regex alternations)
5. **Relationship Inference**: Read found content, infer connections
6. **Cross-Reference**: Link findings, identify patterns
7. **Adaptive Strategy**: Evaluate after each step, adjust approach as needed
<regex_integration>
**Regex Usage Throughout Execution:**
- After any failed exact match Deploy regex pattern immediately
- When searching tech terms Include abbreviation patterns from start
- For people/project names Use flexible matching patterns
- With version numbers Use numeric patterns
- When multiple spellings possible Use alternation patterns
Example execution with regex:
1. Search "docker" Limited results
2. Immediately search \`/docker.*/i\` → Finds "Docker-compose", "Dockerfile", "docker-notes"
3. Search \`/container|docker|k8s/i\` → Domain expansion with regex
4. Success: Comprehensive results through regex flexibility
</regex_integration>
| Complexity | Operations | Example | Strategy |
|------------|-----------|---------|----------|
| Simple | 1-3 | "What did I write about React?" | Entity Search Done |
| Moderate | 4-7 | "Find Docker and K8s notes" | Search Regex fallback Domain expansion |
| Complex | 8-15 | "Compare microservices vs monoliths" | Full planning Multi-search Synthesis |
| Research | 15+ | "Overview of my ML journey" | Comprehensive multi-source analysis |
### Synthesis Phase
Integrate all findings into cohesive response:
1. **Information Integration**: Combine results from all search attempts
2. **Relationship Mapping**: Identify connections between sources
3. **Universal Wiki-Linking**: Apply [[wiki-links]] to ALL vault references
4. **Gap Identification**: Note missing connections or suggest new notes
5. **Structured Response**: Present findings clearly with proper [[wiki-links]]
### Workflow Scaling by Complexity
**Simple (1-3 operations)**: Direct lookup + optional regex
- "What did I write about React?"
- Entity extraction Search Done
**Moderate (4-7 operations)**: Multiple searches with regex and domain expansion
- "Find all my notes about Docker and Kubernetes"
- Search "docker" Regex \`/docker.*/i\` → Domain search "container" → Regex \`/k8s|kubernetes/i\` → Cross-reference
**Complex (8-15 operations)**: Full multi-step planning with progressive strategy
- "Compare my approaches to microservices vs monoliths and relate to current projects"
- Plan: 5-step strategy Execute: Search microservices (regex) Search monoliths (regex) Search projects (directory) Read each Infer relationships Synthesize
**Research-Grade (15+ operations)**: Comprehensive analysis across vault
- "Create a comprehensive overview of my learning journey in machine learning"
- Full planning framework Systematic search across tags, dates, topics Regex patterns for all variations Deep synthesis with temporal analysis
## Core Capabilities
@ -208,7 +152,6 @@ Integrate all findings into cohesive response:
- Creating atomic notes (one idea per note) with proper linking
- Updating existing notes while preserving connections
- Organizing with tags and folder structure
- Using [[note name]] syntax for all vault references
**General Assistance**
- Answering questions using both vault knowledge and general knowledge
@ -217,80 +160,26 @@ Integrate all findings into cohesive response:
## Anti-Patterns to Avoid
Referencing vault content without [[wiki-links]]
Using plain text when [[note name]] syntax is required
Giving up after first failed search attempt
Searching exact literal phrases instead of extracting key entities
Asking permission before searching ("Would you like me to search?")
Asking "Would you like me to create this?" when user already said to create it
Showing what a file would contain instead of actually creating it
Providing incremental progress updates instead of complete results
Missing obvious relationship inferences from found content
Listing all matches when query had directory qualifiers
Providing generic answers when vault contains specific user information
Telling user "not found" without trying progressive search strategies
Referencing vault content without [[wiki-links]]
Giving up after first failed searchalways use progressive strategies
Searching literal phrases instead of extracting key entities
Asking permission when user intent is clear ("Would you like me to...")
Describing what you'd create instead of creating it
Providing generic answers when vault contains specific information
Mimicking historical tool call formats instead of using native functions
## Decision Framework
**Always ask yourself:**
1. **"Have I completed the users request?"** Reflect on what can still be achieved
2. **"Am I using [[wiki-links]] for every vault reference?"** Always required
3. **"Could this information exist in the user's notes?"** Search vault first
4. **"Did my first search fail? Have I tried all progressive tiers?"** Keep searching
5. **"Can I infer the answer from related content I found?"** Read and reason about relationships
6. **"Does this query need multiple search approaches?"** Scale to complexity
7. **"Should I suggest additional related notes?"** Offer connections when helpful
1. "Have I completed the user's request?" Reflect on what can still be achieved
2. "Am I using [[wiki-links]] for every vault reference?" Always required
3. "Could this information exist in the user's notes?" Search vault first
4. "Did my search fail? Have I tried all progressive tiers?" Keep searching
5. "Can I infer the answer from related content I found?" Read and reason
**When uncertain**: Always search the vault first. When search fails, always try alternative strategies before concluding "not found."
## Example Workflows
### Progressive Multi-Tool Search
User: "Who is Elika's mother?"
Poor (gives up immediately):
1. Search: "Elika's mother" No results
2. Response: "Not found in vault"
Good (progressive approach):
1. Extract entity: "Elika"
2. Search: "Elika" Found [[Elika]] note
3. Read content References to "the Queen" and "the Empress" in family context
4. Infer: Context suggests these are family relationships
5. Response: "Based on your [[Elika]] note, her mother is referred to as 'the Queen' or 'the Empress'. This information appears in [[Royal Family Tree]] and is also mentioned in [[Palace History]]."
### Directory-Qualified Query
User: "list my important templates"
Process:
1. Identify qualifier: "important"
2. Check for '/Important templates/' directory
3. Search within that specific path
4. Response: "Here are your important templates: [[template1]], [[template2]], [[template3]]"
### Multi-Source Research Synthesis
User: "what have I learned about RAG systems?"
Process:
1. Search: "RAG" Found multiple notes
2. Search: "retrieval augmented generation" Found additional notes
3. Check tags: #RAG, #ai, #machine-learning
4. Review backlinks to related notes
5. Response: "Based on your research in [[RAG Fundamentals]], [[Vector Databases]], and [[LLM Integration]], you've explored several key aspects:
Your [[RAG Architecture Notes]] cover the retrieval and generation pipeline. You documented implementation challenges in [[RAG Implementation Log]] and compared different approaches in [[RAG vs Fine-tuning]].
This connects to your broader work in [[AI Engineering]] and complements your notes on [[Prompt Engineering]] and [[Context Windows]]. You might also want to link this to [[Production AI Systems]]."
### Knowledge Gap Response
User: "explain transformer architecture"
Process:
1. Search vault for relevant notes
2. No results found
3. Response: "I didn't find notes about transformer architecture in your vault. [Provide general explanation of transformers]. Would you like me to help you create a note about this to add to your knowledge base?"
**When uncertain**: Always search the vault first. Always try alternative strategies before concluding "not found."
---
**Core Philosophy**: Use available tools to support assisting the user. Always use [[wiki-links]] for vault references to build the knowledge graph. Be proactive with vault searches using progressive multi-tier strategiesnever give up after the first attempt. Respect the semantic meaning of the user's organizational structure. Infer relationships from context rather than requiring explicit statements. Scale your search complexity to match the query. Always complete the full request before concluding.
**Core Philosophy**: Act first, explain after. Always use [[wiki-links]] for vault references. Be proactive with vault searches using progressive strategiesnever give up after the first attempt. Scale search complexity to match the query. Complete the full request before concluding.
`;

View file

@ -32,7 +32,11 @@
}
export function updateChatAreaLayout(behavior: ScrollBehavior | undefined, shouldSettle: boolean = false) {
tick().then(() => {
if (layoutUpdateTimeout) {
clearTimeout(layoutUpdateTimeout);
}
const performLayoutCalculations = () => {
if (messageElements.length <= 0 || !chatAreaPaddingElement) {
if (chatAreaPaddingElement) {
chatAreaPaddingElement.style.padding = "0px";
@ -59,7 +63,7 @@
let padding = chatContainer.offsetHeight - paddingTop - paddingBottom - messageSpace;
if (!shouldSettle) {
padding = Math.max(padding, chatContainer.offsetHeight * 0.25);
padding = Math.max(padding, 25);
}
chatAreaPaddingElement.style.padding = `${Math.max(0, padding / 2)}px`;
@ -68,7 +72,21 @@
chatContainer.scroll({ top: chatContainer.scrollHeight, behavior: behavior })
}
});
});
};
if (behavior === "instant" || shouldSettle) {
tick().then(() => {
performLayoutCalculations();
});
} else {
layoutUpdateTimeout = setTimeout(() => {
tick().then(() => {
requestAnimationFrame(() => {
performLayoutCalculations();
});
});
}, 50);
}
}
let autoScroll: boolean = true;
@ -83,6 +101,7 @@
let messageElements: { index: number, element: HTMLElement }[] = [];
let lastProcessedContent: Map<string, string> = new Map<string, string>();
let currentStreamFinalized: boolean = false;
let layoutUpdateTimeout: NodeJS.Timeout | null = null;
function getGreetingByTime(): string {
const hour = new Date().getHours();

View file

@ -450,10 +450,16 @@ describe('Claude', () => {
expect(result).toHaveLength(1);
expect(result[0].content).toHaveLength(1);
expect(result[0].content[0]).toEqual({
type: 'text',
text: '[Legacy Tool Call] search_vault_files\nInput: {"query":"test"}'
});
expect(result[0].content[0].type).toBe('text');
const expected = `<!-- Historical tool call. This action was ALREADY COMPLETED.
Use your native function calling for any NEW operations. -->
{
"name": "search_vault_files",
"args": {
"query": "test"
}
}`;
expect(result[0].content[0].text).toBe(expected);
});
it('should convert function call with empty ID to legacy text format', () => {
@ -476,10 +482,16 @@ describe('Claude', () => {
expect(result).toHaveLength(1);
expect(result[0].content).toHaveLength(1);
expect(result[0].content[0]).toEqual({
type: 'text',
text: '[Legacy Tool Call] search_vault_files\nInput: {"query":"test"}'
});
expect(result[0].content[0].type).toBe('text');
const expected = `<!-- Historical tool call. This action was ALREADY COMPLETED.
Use your native function calling for any NEW operations. -->
{
"name": "search_vault_files",
"args": {
"query": "test"
}
}`;
expect(result[0].content[0].text).toBe(expected);
});
it('should convert function response without ID to legacy text format', () => {
@ -501,10 +513,16 @@ describe('Claude', () => {
expect(result).toHaveLength(1);
expect(result[0].content).toHaveLength(1);
expect(result[0].content[0]).toEqual({
type: 'text',
text: '[Legacy Tool Result] search_vault_files\nResult: ["file1.txt","file2.txt"]'
});
expect(result[0].content[0].type).toBe('text');
const expected = `<!-- Historical tool result. This action was ALREADY COMPLETED. -->
{
"name": "search_vault_files",
"response": [
"file1.txt",
"file2.txt"
]
}`;
expect(result[0].content[0].text).toBe(expected);
});
it('should convert function response with empty ID to legacy text format', () => {
@ -526,10 +544,16 @@ describe('Claude', () => {
expect(result).toHaveLength(1);
expect(result[0].content).toHaveLength(1);
expect(result[0].content[0]).toEqual({
type: 'text',
text: '[Legacy Tool Result] search_vault_files\nResult: ["file1.txt","file2.txt"]'
});
expect(result[0].content[0].type).toBe('text');
const expected = `<!-- Historical tool result. This action was ALREADY COMPLETED. -->
{
"name": "search_vault_files",
"response": [
"file1.txt",
"file2.txt"
]
}`;
expect(result[0].content[0].text).toBe(expected);
});
it('should exclude orphaned function calls without responses', () => {

View file

@ -127,9 +127,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
expect(result).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[0].parts[0].text).toContain('search_vault_files');
expect(result[0].parts[0].text).toContain('meeting notes');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "search_vault_files"');
expect(result[0].parts[0].text).toContain('"args": {');
expect(result[0].parts[0].text).toContain(' "query": "meeting notes"');
});
it('should convert OpenAI function call (no thoughtSignature) to legacy text format', () => {
@ -153,8 +154,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const result = (gemini as any).extractContents([openaiFunctionCall]);
expect(result[0].parts[0].text).toContain('[Legacy Tool Call] read_file');
expect(result[0].parts[0].text).toContain('project.md');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "read_file"');
expect(result[0].parts[0].text).toContain('"args": {');
expect(result[0].parts[0].text).toContain(' "path": "project.md"');
});
it('should convert function response without id to legacy text format', () => {
@ -176,9 +179,11 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const result = (gemini as any).extractContents([claudeResponse]);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Result]');
expect(result[0].parts[0].text).toContain('search_vault_files');
expect(result[0].parts[0].text).toContain('note1.md');
expect(result[0].parts[0].text).toContain('<!-- Historical tool result');
expect(result[0].parts[0].text).toContain('"name": "search_vault_files"');
expect(result[0].parts[0].text).toContain('"response": [');
expect(result[0].parts[0].text).toContain(' "note1.md"');
expect(result[0].parts[0].text).toContain(' "note2.md"');
});
});
@ -303,8 +308,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Find the Claude function call - should be legacy text
const claudeFunctionCall = result.find((r: any) =>
r.parts[0]?.text?.includes('[Legacy Tool Call]') &&
r.parts[0]?.text?.includes('search_vault_files')
r.parts[0]?.text?.includes('<!-- Historical tool call') &&
r.parts[0]?.text?.includes('"name": "search_vault_files"')
);
expect(claudeFunctionCall).toBeDefined();
@ -360,8 +365,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Verify OpenAI function call was converted to legacy format
expect(result).toHaveLength(3);
expect(result[1].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[2].parts[0].text).toContain('[Legacy Tool Result]');
expect(result[1].parts[0].text).toContain('<!-- Historical tool call');
expect(result[1].parts[0].text).toContain('"name": "list_files"');
expect(result[2].parts[0].text).toContain('<!-- Historical tool result');
expect(result[2].parts[0].text).toContain('"name": "list_files"');
});
it('should preserve context when alternating between providers with function calls', () => {
@ -435,11 +442,13 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// First function call should be legacy format
expect(result[1].parts[0]).toHaveProperty('text');
expect(result[1].parts[0].text).toContain('[Legacy Tool Call] func1');
expect(result[1].parts[0].text).toContain('<!-- Historical tool call');
expect(result[1].parts[0].text).toContain('"name": "func1"');
// First response should be legacy format
expect(result[2].parts[0]).toHaveProperty('text');
expect(result[2].parts[0].text).toContain('[Legacy Tool Result] func1');
expect(result[2].parts[0].text).toContain('<!-- Historical tool result');
expect(result[2].parts[0].text).toContain('"name": "func1"');
// Second function call should have proper format
expect(result[4].parts[0]).toHaveProperty('functionCall');
@ -677,7 +686,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const claude = new Claude();
const claudeResult = (claude as any).extractContents([emptyToolIdCall]);
expect(claudeResult[0].content[0].type).toBe('text');
expect(claudeResult[0].content[0].text).toContain('[Legacy Tool Call]');
expect(claudeResult[0].content[0].text).toContain('<!-- Historical tool call');
expect(claudeResult[0].content[0].text).toContain('"name": "search_vault_files"');
// OpenAI should also handle it gracefully (fall back to regular message)
const openai = new OpenAI();
@ -709,7 +719,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const claude = new Claude();
const claudeResult = (claude as any).extractContents([whitespaceToolIdCall]);
expect(claudeResult[0].content[0].type).toBe('text');
expect(claudeResult[0].content[0].text).toContain('[Legacy Tool Call]');
expect(claudeResult[0].content[0].text).toContain('<!-- Historical tool call');
expect(claudeResult[0].content[0].text).toContain('"name": "search_vault_files"');
});
});
@ -776,9 +787,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Gemini should convert to legacy format since there's no thoughtSignature
expect(result).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[0].parts[0].text).toContain('search_vault_files');
expect(result[0].parts[0].text).toContain('project notes');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "search_vault_files"');
expect(result[0].parts[0].text).toContain('"args": {');
expect(result[0].parts[0].text).toContain(' "query": "project notes"');
});
it('should handle OpenAI function response when switching to Gemini', () => {
@ -852,15 +864,16 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// First should be a text message with legacy format (NOT a function_call with undefined call_id)
expect(result[0]).toHaveProperty('role');
expect(result[0]).toHaveProperty('content');
expect(result[0].content).toContain('[Legacy Tool Call]');
expect(result[0].content).toContain('search_vault_files');
expect(result[0].content).toContain('<!-- Historical tool call');
expect(result[0].content).toContain('"name": "search_vault_files"');
expect(result[0]).not.toHaveProperty('type'); // Should be message, not function_call
expect(result[0]).not.toHaveProperty('call_id'); // Should NOT have call_id field
// Second should be a text message with legacy format
expect(result[1]).toHaveProperty('role');
expect(result[1]).toHaveProperty('content');
expect(result[1].content).toContain('[Legacy Tool Result]');
expect(result[1].content).toContain('<!-- Historical tool result');
expect(result[1].content).toContain('"name": "search_vault_files"');
});
it('should handle OpenAI → Gemini → Claude round-trip', () => {
@ -906,7 +919,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Gemini reads OpenAI conversation - function call becomes legacy
const geminiResult = (gemini as any).extractContents(conversation);
const geminiLegacyCall = geminiResult.find((r: any) =>
r.parts[0]?.text?.includes('[Legacy Tool Call]')
r.parts[0]?.text?.includes('<!-- Historical tool call')
);
expect(geminiLegacyCall).toBeDefined();
@ -1006,7 +1019,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
expect(geminiResult.length).toBeGreaterThan(0);
const legacyCalls = geminiResult.filter((r: any) =>
r.parts[0]?.text?.includes('[Legacy Tool Call]')
r.parts[0]?.text?.includes('<!-- Historical tool call')
);
expect(legacyCalls.length).toBe(2); // Both Claude and OpenAI calls
@ -1138,12 +1151,12 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Verify all three function calls are present in each provider's view
const geminiFunctionCalls = geminiResult.filter((r: any) =>
r.parts[0]?.functionCall || r.parts[0]?.text?.includes('[Legacy Tool Call]')
r.parts[0]?.functionCall || r.parts[0]?.text?.includes('<!-- Historical tool call')
);
expect(geminiFunctionCalls.length).toBe(3);
const claudeToolUses = claudeResult.filter((r: any) =>
r.content.some((c: any) => c.type === 'tool_use' || c.text?.includes('[Legacy Tool Call]'))
r.content.some((c: any) => c.type === 'tool_use' || c.text?.includes('<!-- Historical tool call'))
);
expect(claudeToolUses.length).toBeGreaterThan(0);
});
@ -1202,7 +1215,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Gemini reads it (converts to legacy)
const geminiResult = (gemini as any).extractContents(conversation);
const geminiLegacy = geminiResult.find((r: any) =>
r.parts[0]?.text?.includes('[Legacy Tool Call]')
r.parts[0]?.text?.includes('<!-- Historical tool call')
);
expect(geminiLegacy).toBeDefined();
expect(geminiLegacy.parts[0].text).toContain('search_vault_files');
@ -1387,7 +1400,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Each provider should see all 3 function calls (in their own format or legacy)
const claudeToolUses = claudeResult.filter((r: any) =>
r.content.some((c: any) => c.type === 'tool_use' || c.text?.includes('[Legacy Tool Call]'))
r.content.some((c: any) => c.type === 'tool_use' || c.text?.includes('<!-- Historical tool call'))
);
expect(claudeToolUses.length).toBe(3);
@ -1403,7 +1416,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
expect(geminiNativeCalls.length).toBe(1);
const geminiLegacyCalls = geminiResult.filter((r: any) =>
r.parts[0]?.text?.includes('[Legacy Tool Call]')
r.parts[0]?.text?.includes('<!-- Historical tool call')
);
// Note: Orphaned function calls without responses are filtered out by filterConversationContents
// So we verify at least the function calls with responses are present
@ -1430,7 +1443,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const result = (gemini as any).extractContents([content]);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "test_func"');
});
it('should handle whitespace-only thoughtSignature as missing signature (legacy format)', () => {
@ -1453,7 +1467,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Whitespace-only signature is trimmed and treated as missing
// Should fall back to legacy text format
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "test_func"');
});
it('should gracefully handle conversation with only legacy format calls', () => {
@ -1484,8 +1499,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const result = (gemini as any).extractContents(conversation);
// All function calls/responses should be in legacy format
expect(result[1].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[2].parts[0].text).toContain('[Legacy Tool Result]');
expect(result[1].parts[0].text).toContain('<!-- Historical tool call');
expect(result[1].parts[0].text).toContain('"name": "func1"');
expect(result[2].parts[0].text).toContain('<!-- Historical tool result');
expect(result[2].parts[0].text).toContain('"name": "func1"');
});
it('should handle both toolId and thoughtSignature present (defensive)', () => {
@ -1558,7 +1575,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
const claude = new Claude();
const claudeResult = (claude as any).extractContents([content]);
expect(claudeResult[0].content[0].type).toBe('text');
expect(claudeResult[0].content[0].text).toContain('[Legacy Tool Call]');
expect(claudeResult[0].content[0].text).toContain('<!-- Historical tool call');
expect(claudeResult[0].content[0].text).toContain('"name": "test_func"');
// OpenAI should also fall back gracefully
const openai = new OpenAI();

View file

@ -524,9 +524,10 @@ describe('Gemini', () => {
expect(result[0].role).toBe(Role.Model);
expect(result[0].parts).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call]');
expect(result[0].parts[0].text).toContain('search_vault_files');
expect(result[0].parts[0].text).toContain('Input:');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "search_vault_files"');
expect(result[0].parts[0].text).toContain('"args": {');
expect(result[0].parts[0].text).toContain(' "query": "test"');
});
it('should fall back to legacy text format for function call with empty thoughtSignature', () => {
@ -551,7 +552,10 @@ describe('Gemini', () => {
expect(result).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Call] read_file');
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "read_file"');
expect(result[0].parts[0].text).toContain('"args": {');
expect(result[0].parts[0].text).toContain(' "path": "note.md"');
});
it('should convert function response to Gemini format', () => {
@ -601,9 +605,11 @@ describe('Gemini', () => {
expect(result).toHaveLength(1);
expect(result[0].parts).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Result]');
expect(result[0].parts[0].text).toContain('search_vault_files');
expect(result[0].parts[0].text).toContain('Result:');
expect(result[0].parts[0].text).toContain('<!-- Historical tool result');
expect(result[0].parts[0].text).toContain('"name": "search_vault_files"');
expect(result[0].parts[0].text).toContain('"response": [');
expect(result[0].parts[0].text).toContain(' "file1.txt"');
expect(result[0].parts[0].text).toContain(' "file2.txt"');
});
it('should fall back to legacy text format for function response with empty id', () => {
@ -625,7 +631,10 @@ describe('Gemini', () => {
expect(result).toHaveLength(1);
expect(result[0].parts[0]).toHaveProperty('text');
expect(result[0].parts[0].text).toContain('[Legacy Tool Result] read_file');
expect(result[0].parts[0].text).toContain('<!-- Historical tool result');
expect(result[0].parts[0].text).toContain('"name": "read_file"');
expect(result[0].parts[0].text).toContain('"response": {');
expect(result[0].parts[0].text).toContain(' "content": "file contents"');
});
it('should handle invalid JSON in function call gracefully', () => {
@ -849,10 +858,11 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionCallToText(parsedContent);
expect(result).toContain('[Legacy Tool Call]');
expect(result).toContain('search_vault_files');
expect(result).toContain('Input:');
expect(result).toContain('"query":"test notes"');
expect(result).toContain('<!-- Historical tool call');
expect(result).toContain('This action was ALREADY COMPLETED');
expect(result).toContain('"name": "search_vault_files"');
expect(result).toContain('"args": {');
expect(result).toContain(' "query": "test notes"');
});
it('should format complex arguments correctly', () => {
@ -869,11 +879,14 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionCallToText(parsedContent);
expect(result).toContain('[Legacy Tool Call] write_file');
expect(result).toContain('Input:');
expect(result).toContain('"path":"note.md"');
expect(result).toContain('"content":"Hello World"');
expect(result).toContain('"metadata"');
expect(result).toContain('<!-- Historical tool call');
expect(result).toContain('"name": "write_file"');
expect(result).toContain('"args": {');
expect(result).toContain(' "path": "note.md"');
expect(result).toContain(' "content": "Hello World"');
expect(result).toContain(' "metadata": {');
expect(result).toContain(' "tags": [');
expect(result).toContain(' "important"');
});
it('should handle function call with empty args', () => {
@ -886,7 +899,13 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionCallToText(parsedContent);
expect(result).toBe('[Legacy Tool Call] list_files\nInput: {}');
const expected = `<!-- Historical tool call. This action was ALREADY COMPLETED.
Use your native function calling for any NEW operations. -->
{
"name": "list_files",
"args": {}
}`;
expect(result).toBe(expected);
});
});
@ -901,11 +920,13 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionResponseToText(parsedContent);
expect(result).toContain('[Legacy Tool Result]');
expect(result).toContain('search_vault_files');
expect(result).toContain('Result:');
expect(result).toContain('file1.txt');
expect(result).toContain('file2.txt');
expect(result).toContain('<!-- Historical tool result');
expect(result).toContain('This action was ALREADY COMPLETED');
expect(result).toContain('"name": "search_vault_files"');
expect(result).toContain('"response": [');
expect(result).toContain(' "file1.txt"');
expect(result).toContain(' "file2.txt"');
expect(result).toContain(' "file3.txt"');
});
it('should format complex response objects correctly', () => {
@ -921,10 +942,13 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionResponseToText(parsedContent);
expect(result).toContain('[Legacy Tool Result] read_file');
expect(result).toContain('Result:');
expect(result).toContain('"content":"File contents here"');
expect(result).toContain('"metadata"');
expect(result).toContain('<!-- Historical tool result');
expect(result).toContain('"name": "read_file"');
expect(result).toContain('"response": {');
expect(result).toContain(' "content": "File contents here"');
expect(result).toContain(' "metadata": {');
expect(result).toContain(' "size": 1024');
expect(result).toContain(' "modified": "2024-01-01"');
});
it('should handle empty response', () => {
@ -937,7 +961,12 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionResponseToText(parsedContent);
expect(result).toBe('[Legacy Tool Result] delete_file\nResult: null');
const expected = `<!-- Historical tool result. This action was ALREADY COMPLETED. -->
{
"name": "delete_file",
"response": null
}`;
expect(result).toBe(expected);
});
it('should handle string response', () => {
@ -950,7 +979,12 @@ describe('Gemini', () => {
const result = (gemini as any).convertFunctionResponseToText(parsedContent);
expect(result).toBe('[Legacy Tool Result] get_status\nResult: "Success"');
const expected = `<!-- Historical tool result. This action was ALREADY COMPLETED. -->
{
"name": "get_status",
"response": "Success"
}`;
expect(result).toBe(expected);
});
});
});