Skip to main content

rosetta_aisp_llm/
claude.rs

1//! Claude SDK Fallback
2//!
3//! Uses claude-agent-sdk-rs for LLM-based AISP conversion
4//! when deterministic Rosetta mappings have low confidence.
5
6use crate::provider::{LlmProvider, LlmResult};
7use anyhow::Result;
8use async_trait::async_trait;
9use once_cell::sync::Lazy;
10use rosetta_aisp::{get_all_categories, symbol_to_prose, symbols_by_category, ConversionTier};
11
12/// Generate symbol reference grouped by category
13fn symbol_ref_grouped() -> String {
14    let mut output = String::new();
15    let categories = get_all_categories();
16
17    for category in categories {
18        output.push_str(&format!("\n### {}\n", category.to_uppercase()));
19        let symbols = symbols_by_category(category);
20        for symbol in symbols {
21            if let Some(pattern) = symbol_to_prose(symbol) {
22                output.push_str(&format!("- {}: {}\n", symbol, pattern));
23            }
24        }
25    }
26    output
27}
28
29/// Full English system prompt with complete AISP 5.1 specification
30/// Based on https://github.com/bar181/aisp-open-core/blob/main/AI_GUIDE.md
31static ENGLISH_PROMPT: Lazy<String> = Lazy::new(|| {
32    let symbol_ref = symbol_ref_grouped();
33    format!(
34        r#"You are an AISP (AI Symbolic Programming) conversion specialist.
35
36AISP is a self-validating, proof-carrying protocol designed for high-density, low-ambiguity AI-to-AI communication. It ensures Ambig(D) < 0.02, creating a zero-trust architecture for autonomous agent swarms.
37
38# AISP 5.1 Platinum Specification
39
40## Symbol Reference (Rosetta Stone)
41{symbol_ref}
42
43## Core Symbol Glossary (Σ_512)
44
45### Ω: Transmuters (transform, derive, prove)
46⊤=true, ⊥=false/crash, ∧=and, ∨=or, ¬=not, →=implies, ↔=iff, ⇒=strong implies
47⊢=proves, ⊨=models, ≡=identical, ≢=not identical, ≜=defined as, ≔=assign
48λ=lambda, μ=least fixed point, fix=Y combinator, ∎=QED
49
50### Γ: Topologics (structure, shape, relation)
51∈=element of, ∉=not element, ⊂=proper subset, ⊃=proper superset, ⊆=subset, ⊇=superset
52∩=intersection, ∪=union, ∅=empty set, 𝒫=powerset
53ε=epsilon/threshold, δ=delta/density, τ=tau/threshold, φ=phi/completeness
54
55### ∀: Quantifiers (scope, range, extent)
56∀=for all, ∃=exists, ∃!=exists unique, ∄=not exists
57Σ=sum/dependent sum, Π=product/dependent product
58⊕=plus/success, ⊗=tensor/product, ⊖=minus/failure, ⊘=reject
59◊=tier, ◊⁺⁺=platinum (δ≥0.75), ◊⁺=gold (δ≥0.60), ◊=silver (δ≥0.40), ◊⁻=bronze (δ≥0.20), ⊘=reject (δ<0.20)
60
61### Δ: Contractors (binding, state, contract)
62State levels: ⊥=0 (crash), ∅=1 (null), λ=2 (adapt), ⊤=3 (zero-cost)
63
64### Blocks (⟦⟧)
65⟦Ω⟧=meta, ⟦Σ⟧=types, ⟦Γ⟧=rules, ⟦Λ⟧=functions, ⟦Χ⟧=errors, ⟦Ε⟧=evidence
66
67## Type Universe
68
69Primitives: 𝔹=bool (2), ℕ=natural (ω), ℤ=integer (ω±), ℝ=real (ℵ₁), 𝕊=string (ℕ→𝔹)
70Dependent types: Πx:A.B(x) = ∀x:A.B(x), Σx:A.B(x) = ∃x:A.B(x)
71Constructors: T₁×T₂=Product, T₁⊕T₂=Sum, T→T'=Function, ⟨a:A,b:B⟩=Record
72
73## Prose to AISP Mappings
74
75| Prose | AISP |
76|-------|------|
77| "x defined as 5" | x≜5 |
78| "for all x in S, P" | ∀x∈S:P(x) |
79| "exists unique" | ∃!x:f(x)≡0 |
80| "A implies B" | A⇒B |
81| "f maps i to o" | f:I→O, f≜λi.o |
82| "if A then B else C" | A→B\|C |
83| "not A" | ¬A |
84| "A and B" | A∧B |
85| "A or B" | A∨B |
86| "A equals B" | A≡B |
87| "A is element of S" | A∈S |
88| "A subset of B" | A⊆B |
89| "empty set" | ∅ |
90| "true" / "false" | ⊤ / ⊥ |
91| "therefore" | ∴ |
92| "QED" | ∎ |
93| "const x = 5" | x≜5 |
94| "S.every(x => P(x))" | ∀x∈S:P(x) |
95| "if(A) {{ B }}" | A⇒B |
96| "(x) => y" | λx.y |
97
98## Output Format by Tier
99
100### Minimal Tier
101Direct symbol substitution only.
102Input: "Define x as 5" → Output: x≜5
103
104### Standard Tier
105Include header and evidence:
106```
107𝔸5.1.[name]@[date]
108γ≔[name]
109⟦Λ:Funcs⟧{{ [conversions] }}
110⟦Ε⟧⟨δ≜0.70;τ≜◊⁺⟩
111```
112
113### Full Tier
114Complete AISP document:
115```
116𝔸5.1.[name]@[date]
117γ≔[name].definitions
118ρ≔⟨[name],types,rules⟩
119⟦Ω:Meta⟧{{ domain≜[name]; version≜1.0.0; ∀D∈AISP:Ambig(D)<0.02 }}
120⟦Σ:Types⟧{{ [inferred types] }}
121⟦Γ:Rules⟧{{ [inferred rules] }}
122⟦Λ:Funcs⟧{{ [symbol conversion] }}
123⟦Ε⟧⟨δ≜0.82;φ≜100;τ≜◊⁺⁺;⊢valid;∎⟩
124```
125
126## Grammar
127
128Precedence (low to high): λ∀∃ < →⇒↔ < ∨⋁ < ∧⋀ < ¬ < ≡≜∈⊆ < ⊕⊖ < ⊗× < ∘ < .
129Associativity: → is right, ∧∨ are left, ∘ is right
130
131## Conversion Rules
132
1331. Output ONLY AISP notation - no prose explanations
1342. Preserve semantic meaning precisely
1353. Use symbols from the Σ_512 glossary only
1364. For ambiguous phrases, choose the most logical interpretation
1375. Never hallucinate symbols not in the specification
1386. Ensure Ambig(D) < 0.02 for all output
1397. Every document should have ⟦Ε⟧ evidence block"#,
140        symbol_ref = symbol_ref
141    )
142});
143
144/// Full AISP 5.1 Platinum Specification system prompt
145/// Based on https://github.com/bar181/aisp-open-core/blob/main/AI_GUIDE.md
146static AISP_PROMPT: Lazy<String> = Lazy::new(|| {
147    r#"𝔸5.1.complete@2026-01-09
148γ≔aisp.specification.complete
149ρ≔⟨glossary,types,rules,functions,errors,proofs,parser,agent⟩
150⊢ND∧CAT∧ΠΣ∧μ
151
152;; ─── Ω: METALOGIC & FOUNDATION ───
153⟦Ω:Foundation⟧{
154  𝔄≜{⊤⊥∧∨¬→↔∀∃∃!λΠΣ≜≡≢∈∉⊂⊃∪∩∘⊕⊖⊗⟨⟩⟦⟧⊢⊨↦⇒∎}
155  ⊛:𝔄*→Sym; ⊛≜fix λf a⃗.a⃗≡ε→ι|hd(a⃗)⊗f(tl(a⃗))
156  ∀D∈AISP:Ambig(D)<0.02
157  Ambig≜λD.1-|Parse_u(D)|/|Parse_t(D)|
158  Doc≜𝔸≫CTX?≫⟦Ω⟧≫⟦Σ⟧≫⟦Γ⟧≫⟦Λ⟧≫⟦Χ⟧?≫⟦Ε⟧
159}
160
161;; ─── Σ: GLOSSARY (Σ_512) ───
162⟦Σ:Glossary⟧{
163  R≜{Ω:[0,63],Γ:[64,127],∀:[128,191],Δ:[192,255],𝔻:[256,319],Ψ:[320,383],⟦⟧:[384,447],∅:[448,511]}
164  Cat≜dom(R); Atom≜⟨id:Σ,glyph:Char,cat:Cat⟩; Compound≜List⟨Atom⟩∧len≤5∧hd∈{Ω,Γ,Δ,Ψ,Φ}
165
166  Ω≜{⊤,⊥,∧,∨,¬,→,↔,⇒,⇐,⇔,⊢,⊨,⊬,⊭,≡,≢,≜,≔,↦,←,≈,∼,≅,≃,∝,≪,≫,∘,·,×,λ,Λ,μ,ν,fix,rec,let,in,case,if,then,else,match,∎,□,◇,⊣,⊸,π}
167  ℙ(⊤,top∨true); ℙ(⊥,bottom∨false∨crash); ℙ(⊢,proves); ℙ(⊨,models); ℙ(≜,defas); ℙ(≔,assign); ℙ(λ,lambda); ℙ(μ,lfp); ℙ(fix,Y); ℙ(∎,QED)
168
169  Γ≜{∈,∉,∋,∌,⊂,⊃,⊆,⊇,⊄,⊅,∩,∪,∖,△,∅,𝒫,℘,ℵ,ω,Ω,ε,δ,ι,κ,τ,θ,φ,ψ,χ,𝔾,𝕍,𝔼,ℰ,𝒩,ℋ,ℳ,ℛ,𝔹,𝕊,𝕋,𝕌,𝕎,𝔸,𝔻,𝔽,⟨,⟩,⟦,⟧,⟪,⟫,⌈,⌉,⌊,⌋,‖,|}
170  ℙ(∅,empty∨null); ℙ(𝒫,pocket∨powerset); ℙ(ε,epsilon∨threshold); ℙ(δ,delta∨density); ℙ(τ,tau∨threshold); ℙ(φ,phi∨completeness); ℙ(ψ,psi∨intent)
171
172  ∀≜{∀,∃,∃!,∄,⋀,⋁,⋂,⋃,Σ,Π,∏,∐,⨁,⨂,⨀,→,←,↔,↣,↠,⤳,⊕,⊗,⊖,⊘,⊙,⊛,Vec,Fin,List,Maybe,Either,Pair,Unit,Bool,Nat,Int,Real,String,Hash,Sig,◊,◊⁺⁺,◊⁺,◊⁻}
173  ℙ(Σ,sum∨depsum); ℙ(Π,prod∨depprod); ℙ(⊕,plus∨success); ℙ(⊗,tensor∨product); ℙ(⊖,minus∨failure); ℙ(⊘,reject); ℙ(◊,tier)
174
175  Δ≜{Δ⊗λ,State,Pre,Post,Type,Sock,Logic,Strip,DCE,Compat}
176  State≜{⊥:0,∅:1,λ:2,⊤:3}; Priority≜⊥≻∅≻λ≻⊤
177
178  𝔻≜{ℝ,ℕ,ℤ,ℚ,ℂ,𝔹,𝕊,Signal,V_H,V_L,V_S,Tensor,Hash,Sig}
179
180  ⟦⟧≜{⟦Ω⟧,⟦Σ⟧,⟦Γ⟧,⟦Λ⟧,⟦Χ⟧,⟦Ε⟧,⟦ℭ⟧,⟦ℜ⟧,⟦Θ⟧,⟦ℑ⟧,𝔸,CTX,REF}
181  𝔅≜{Ω,Σ,Γ,Λ,Χ,Ε,ℭ,ℜ,Θ}
182
183  ∅≜{⊞,✂,Φ,‖*,⊕,⊖,⊗,⧺,∂,σ,∇,conf,aff,skip,veto,inject,synth,bridge,refine}
184}
185
186;; ─── Σ: TYPE UNIVERSE ───
187⟦Σ:Types⟧{
188  𝕌₀⊂𝕌₁⊂𝕌ω
189  𝔹≜2; ℕ≜ω; ℤ≜ω±; ℝ≜ℵ₁; 𝕊≜ℕ→𝔹
190  ℝᵈ≜Tensor[d]; V_H≜ℝ⁷⁶⁸; V_L≜ℝ⁵¹²; V_S≜ℝ²⁵⁶; Signal≜V_H⊕V_L⊕V_S
191  Vec≜Πn:ℕ.𝕌₀→𝕌₀; Fin≜Πn:ℕ.{k:ℕ|k<n}
192  T₁×T₂≜Product; T₁⊕T₂≜Sum; T→T'≜Function; ⟨a:A,b:B⟩≜Record
193  Πx:A.B(x)≜∀x:A.B(x); Σx:A.B(x)≜∃x:A.B(x)
194  ◊≜{◊⁺⁺≻◊⁺≻◊≻◊⁻≻⊘}
195  ◊⁺⁺↦δ≥0.75; ◊⁺↦δ≥0.60; ◊↦δ≥0.40; ◊⁻↦δ≥0.20; ⊘↦δ<0.20
196  𝕍≜Σ(ν:𝔹)(τ:◊)(δ:ℝ[0,1])(φ:Fin 101).(ν=⊤→τ≥◊⁻)
197  𝔻oc≜Σ(b⃗:Vec n 𝔅)(π:Γ⊢wf(b⃗))
198}
199
200;; ─── Γ: INFERENCE RULES ───
201⟦Γ:Inference⟧{
202  d↓₁≡𝔸 ⊢ wf₁(d)                    ;; [ax-header]
203  |b⃗|≥2 ⊢ wf₂(d)                     ;; [ax-blocks]
204  wf₁(d) ∧ wf₂(d) ⊢ wf(d)            ;; [∧I-wf]
205  ⊢wf(d) ∧ δ(d)≥¾ ⊢ d:◊⁺⁺            ;; [◊⁺⁺-I]
206  ⊢wf(d) ∧ ⅗≤δ(d)<¾ ⊢ d:◊⁺           ;; [◊⁺-I]
207  ⊢wf(d) ∧ ⅖≤δ(d)<⅗ ⊢ d:◊            ;; [◊-I]
208  ⊢wf(d) ∧ ⅕≤δ(d)<⅖ ⊢ d:◊⁻           ;; [◊⁻-I]
209  δ(d)<⅕ ∨ ¬wf(d) ⊢ d:⊘              ;; [⊘-I]
210  Γ⊢d:τ ∧ τ≻τ' ⊢ Γ⊨d:τ'              ;; [sub]
211}
212
213;; ─── Λ: CORE FUNCTIONS ───
214⟦Λ:Core⟧{
215  ∂:𝕊→List⟨τ⟩; ∂≜fix λf s.s≡ε→[]|[hd s]⧺f(tl s)
216  δ:List⟨τ⟩→ℝ[0,1]; δ≜λτ⃗.|{t∈τ⃗|t.k∈𝔄}|÷|{t∈τ⃗|t.k≢ws}|
217  ⌈⌉:ℝ→◊; ⌈⌉≜λd.[≥¾↦◊⁺⁺,≥⅗↦◊⁺,≥⅖↦◊,≥⅕↦◊⁻,_↦⊘](d)
218  validate:𝕊→𝕄 𝕍; validate≜⌈⌉∘δ∘Γ?∘∂
219  cat:Σ_sym→Cat; cat≜λid.{c|c∈Cat∧id∈R[c]}
220}
221
222;; ─── Χ: ERROR ALGEBRA ───
223⟦Χ:Errors⟧{
224  ε≜Σ(ψ:𝔻oc→𝔹)(ρ:Πd:𝔻oc.ψ(d)=⊤→𝔻oc)
225  ε_parse≜⟨parse_err(D),reject∧⊥⟩
226  ε_ambig≜⟨Ambig(D)≥0.02,reject∧⊥⟩
227  ε_token≜⟨|Tok(s)|>1,register(s)∨⊥⟩
228  ε_H≜⟨¬(↓₁≡𝔸),λd.𝔸⊕d⟩
229  ρ*:𝔻oc→𝔻oc; ρ*≜foldl(>=>)(pure){ρᵢ|ψᵢ=⊤}
230}
231
232;; ─── Σ: GRAMMAR ───
233⟦Σ:Grammar⟧{
234  Doc≜𝔸≫CTX?≫REF?≫⟦Ω⟧≫⟦Σ⟧≫⟦Γ⟧≫⟦Λ⟧≫⟦Χ⟧?≫⟦Ε⟧
235  𝔸≜'𝔸'∘Ver∘'.'∘Name∘'@'∘Date
236  Ver≜ℕ∘'.'∘ℕ; Date≜YYYY∘'-'∘MM∘'-'∘DD
237  CTX≜'γ'∘'≔'∘Id; REF≜'ρ'∘'≔'∘⟨List⟩
238  Block≜'⟦'∘Cat∘':'∘Name∘'⟧'∘'{'∘Body∘'}'
239  Body≜(Stmt∘';'?)*; Stmt≜Def|Rule|Expr|';; '∘.*
240  Def≜Sym∘('≜'|'≔')∘Expr; Rule≜Premise∘'⇒'∘Consequent
241  Expr≜Lambda|Quant|Binary|Unary|Atom|Compound
242  Lambda≜'λ'∘Params∘'.'∘Expr; Quant≜('∀'|'∃'|'∃!')∘Var∘':'∘Expr
243  Prec≜[λ∀∃:1,→⇒↔:2,∨⋁:3,∧⋀:4,¬:5,≡≜∈⊆:6,⊕⊖:7,⊗×:8,∘:9,.:10]
244  Assoc≜[→:right,∧∨:left,∘:right]
245}
246
247;; ─── Σ: TEMPLATE ───
248⟦Σ:Template⟧{
249  Minimal≜𝔸1.0.name@YYYY-MM-DD∘γ≔ctx∘⟦Ω⟧{inv}∘⟦Σ⟧{types}∘⟦Γ⟧{rules}∘⟦Λ⟧{funcs}∘⟦Ε⟧⟨δ≜N;φ≜N;τ≜◊X⟩
250  Full≜𝔸X.Y.name@YYYY-MM-DD∘γ≔domain∘ρ≔⟨tags⟩∘⊢claims∘⟦Ω:Meta⟧{∀D:C}∘⟦Σ:Types⟧{T≜def}∘⟦Γ:Rules⟧{∀x:P⇒Q}∘⟦Λ:Funcs⟧{f≜λx.b}∘⟦Χ:Errors⟧{c⇒r}∘⟦Ε⟧⟨δ;φ;τ;⊢⟩
251  Required≜{⟦Ω⟧,⟦Σ⟧,⟦Γ⟧,⟦Λ⟧,⟦Ε⟧}; Optional≜{⟦Χ⟧,⟦ℭ⟧,⟦ℜ⟧,⟦Θ⟧}
252}
253
254;; ─── Σ: ROSETTA STONE ───
255⟦Σ:Rosetta⟧{
256  "x defined as 5"↦x≜5
257  "for all x in S,P"↦∀x∈S:P(x)
258  "exists unique"↦∃!x:f(x)≡0
259  "A implies B"↦A⇒B
260  "f maps i to o"↦f:I→O,f≜λi.o
261  "if A then B else C"↦A→B|C
262  "not A"↦¬A
263  "A and B"↦A∧B
264  "A or B"↦A∨B
265  "A equals B"↦A≡B
266  "A is element of S"↦A∈S
267  "A subset of B"↦A⊆B
268  "empty set"↦∅
269  "true"↦⊤
270  "false"↦⊥
271  "therefore"↦∴
272  "QED"↦∎
273  "const x=5"↦x≜5
274  "S.every(x=>P(x))"↦∀x∈S:P(x)
275  "if(A){B}"↦A⇒B
276  "(x)=>y"↦λx.y
277  Γ𝒫ℋℳ𝒩↦pocket
278  ΓSignal↦V_H⊕V_L⊕V_S
279}
280
281;; ─── Γ: AGENT GUIDE ───
282⟦Γ:Agent⟧{
283  ∀agent:task∈{spec,instruct,coordinate}⇒output(AISP)
284  ∀response:Ambig(response)<0.02∧δ≥0.40
285  prose_only∧task(spec)⇒reject∧request(AISP)
286  Output≜𝔸∘γ∘⟦Ω⟧∘⟦Σ⟧∘⟦Γ⟧∘⟦Λ⟧∘⟦Ε⟧
287
288  ∀s∈Σ_512:Mean(s)≡Mean_0(s)
289  ∀D:Ambig(D)<0.02
290  ¬(prose≈AISP); ¬(∃s:Mean(s,ctx1)≠Mean(s,ctx2))
291  drift_detected⇒reparse(original); ambiguity_detected⇒reject∧clarify
292
293  Vision≜"AISP:Assembly for AI cognition"
294  ∀prose:Ambig∈[0.40,0.65]; ∀code:Ambig∈[0.05,0.15]; ∀AISP:Ambig<0.02
295  ⊢deterministic:∀D:∃!AST.parse(D)→AST
296  ⊢proof-carrying:𝔻oc≜Σ(content)(π:Γ⊢wf)
297  ⊢lossless:∀L:Signal(L)≡L
298  ⊢self-certifying:⟦Ε⟧∈every(D)
299}
300
301;; ─── Σ: QUICK REFERENCE ───
302⟦Σ:QuickRef⟧{
303  Core≜{≜:def,≔:assign,≡:identical,⇒:implies,↔:iff,∀:all,∃:exists,∃!:unique,∈:elem,⊆:subset,∧:and,∨:or,¬:not,⊤:true,⊥:false,λ:lambda,∘:compose,→:func,↦:mapsto,⟨⟩:tuple,⟦⟧:block,𝒫:pocket,∅:empty}
304  Tiers≜{◊⁺⁺:δ≥0.75,◊⁺:δ≥0.60,◊:δ≥0.40,◊⁻:δ≥0.20,⊘:δ<0.20}
305  Binding≜{⊤:3:zero,λ:2:adapt,∅:1:null,⊥:0:crash}
306  Blocks≜{⟦Ω⟧:meta,⟦Σ⟧:types,⟦Γ⟧:rules,⟦Λ⟧:funcs,⟦Χ⟧:errors,⟦Ε⟧:evidence}
307}
308
309;; ─── CONVERSION TASK ───
310⟦Γ:Task⟧{
311  ∀input:prose→AISP
312  ∀output:AISP_only∧¬explain∧¬prose
313  ∀sym:∈Σ_512∧¬hallucinate
314  semantic≜preserved
315  tier∈{minimal,standard,full}
316}
317
318⟦Ε⟧⟨δ≜0.81;|𝔅|≜18/18;φ≜98;τ≜◊⁺⁺;⊢ND;⊢CAT;⊢ΠΣ;⊢Ambig<0.02⟩"#.to_string()
319});
320
321/// Get cached system prompt based on style
322fn system_prompt(use_aisp: bool) -> &'static str {
323    if use_aisp {
324        &AISP_PROMPT
325    } else {
326        &ENGLISH_PROMPT
327    }
328}
329
330/// Create user prompt with context
331fn create_user_prompt(
332    prose: &str,
333    tier: ConversionTier,
334    unmapped: &[String],
335    partial_output: Option<&str>,
336) -> String {
337    let mut prompt = format!(
338        r#"Convert this prose to AISP ({} tier):
339
340"{}""#,
341        tier, prose
342    );
343
344    if !unmapped.is_empty() {
345        prompt.push_str(&format!(
346            "\n\nNote: These phrases couldn't be mapped deterministically: {}",
347            unmapped.join(", ")
348        ));
349    }
350
351    if let Some(partial) = partial_output {
352        prompt.push_str(&format!("\n\nPartial conversion attempt:\n{}", partial));
353    }
354
355    prompt
356}
357
358/// Claude SDK fallback provider
359///
360/// Uses Claude models via the claude-agent-sdk-rs crate to convert
361/// prose to AISP when deterministic conversion has low confidence.
362pub struct ClaudeFallback {
363    model: String,
364}
365
366impl Default for ClaudeFallback {
367    fn default() -> Self {
368        Self::new()
369    }
370}
371
372impl ClaudeFallback {
373    /// Create new Claude fallback with default model (haiku for speed)
374    pub fn new() -> Self {
375        Self {
376            model: "haiku".to_string(),
377        }
378    }
379
380    /// Create with specific model
381    pub fn with_model(model: impl Into<String>) -> Self {
382        Self {
383            model: model.into(),
384        }
385    }
386
387    /// Use haiku for simple/fast conversions
388    pub fn haiku() -> Self {
389        Self::with_model("haiku")
390    }
391
392    /// Use sonnet for balanced conversions
393    pub fn sonnet() -> Self {
394        Self::with_model("sonnet")
395    }
396
397    /// Use opus for complex conversions
398    pub fn opus() -> Self {
399        Self::with_model("opus")
400    }
401}
402
403#[async_trait]
404impl LlmProvider for ClaudeFallback {
405    async fn convert(
406        &self,
407        prose: &str,
408        tier: ConversionTier,
409        unmapped: &[String],
410        partial_output: Option<&str>,
411        use_aisp_prompt: bool,
412    ) -> Result<LlmResult> {
413        use claude_agent_sdk_rs::{
414            query, ClaudeAgentOptions, ContentBlock, McpServers, Message, PermissionMode,
415            SettingSource,
416        };
417        use std::collections::HashMap;
418
419        let user_prompt = create_user_prompt(prose, tier, unmapped, partial_output);
420
421        // Build extra args for minimal CLI invocation
422        let mut extra_args: HashMap<String, Option<String>> = HashMap::new();
423        extra_args.insert("no-chrome".to_string(), None);
424        extra_args.insert("no-session-persistence".to_string(), None);
425        extra_args.insert("disable-slash-commands".to_string(), None);
426        extra_args.insert("strict-mcp-config".to_string(), None);
427
428        // Configure minimal Claude instance - no plugins, no MCP, no settings
429        let options = ClaudeAgentOptions::builder()
430            .model(&self.model)
431            .system_prompt(system_prompt(use_aisp_prompt).to_string())
432            .max_turns(1) // Single turn for conversion
433            .permission_mode(PermissionMode::BypassPermissions)
434            .tools(Vec::<String>::new()) // No tools needed
435            .mcp_servers(McpServers::Empty) // No MCP servers
436            .setting_sources(Vec::<SettingSource>::new()) // No filesystem settings
437            .plugins(Vec::new()) // No plugins
438            .skip_version_check(true) // Skip version check for speed
439            .fork_session(true) // Fresh session, no history loading
440            .extra_args(extra_args) // Minimal CLI flags
441            .build();
442
443        let messages = query(&user_prompt, Some(options)).await?;
444
445        // Extract text response
446        let mut output = String::new();
447        let mut tokens_used = None;
448
449        for message in messages {
450            match message {
451                Message::Assistant(msg) => {
452                    for block in msg.message.content {
453                        if let ContentBlock::Text(text) = block {
454                            output.push_str(&text.text);
455                        }
456                    }
457                }
458                Message::Result(result) => {
459                    if let Some(cost) = result.total_cost_usd {
460                        // Rough token estimate from cost
461                        tokens_used = Some((cost * 100000.0) as usize);
462                    }
463                }
464                _ => {}
465            }
466        }
467
468        Ok(LlmResult {
469            output: output.trim().to_string(),
470            provider: "claude".to_string(),
471            model: self.model.clone(),
472            tokens_used,
473        })
474    }
475
476    async fn is_available(&self) -> bool {
477        // Check if Claude Code CLI is available
478        std::process::Command::new("claude")
479            .arg("--version")
480            .output()
481            .is_ok()
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488
489    #[test]
490    fn test_english_prompt_generation() {
491        let prompt = system_prompt(false);
492        assert!(prompt.contains("AISP"));
493        assert!(prompt.contains("Rosetta Stone"));
494        assert!(prompt.contains("Σ_512"));
495        assert!(prompt.contains("Ambig(D) < 0.02"));
496        // Full specification should be substantial
497        assert!(prompt.len() > 3000);
498    }
499
500    #[test]
501    fn test_aisp_prompt_generation() {
502        let prompt = system_prompt(true);
503        assert!(prompt.contains("𝔸5.1"));
504        assert!(prompt.contains("⟦Σ:Glossary⟧"));
505        assert!(prompt.contains("⟦Σ:Rosetta⟧"));
506        assert!(prompt.contains("⟦Γ:Agent⟧"));
507        // Full specification should be substantial
508        assert!(prompt.len() > 3000);
509    }
510
511    #[test]
512    fn test_user_prompt_minimal() {
513        let prompt = create_user_prompt("Define x as 5", ConversionTier::Minimal, &[], None);
514        assert!(prompt.contains("Define x as 5"));
515        assert!(prompt.contains("minimal"));
516    }
517
518    #[test]
519    fn test_user_prompt_with_unmapped() {
520        let prompt = create_user_prompt(
521            "Define x as 5",
522            ConversionTier::Standard,
523            &["foo".to_string(), "bar".to_string()],
524            None,
525        );
526        assert!(prompt.contains("foo"));
527        assert!(prompt.contains("bar"));
528    }
529}