@@ -31,6 +31,20 @@ struct DiagramOut {
3131 task_id : Option < String > ,
3232 plan_slug : Option < String > ,
3333 persisted : bool ,
34+ /// Provider/model that generated this diagram, when known. Stamped for both
35+ /// persisted and ephemeral diagrams so the gallery can show it.
36+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
37+ provider : Option < String > ,
38+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
39+ model : Option < String > ,
40+ }
41+
42+ /// Provider/model that issued a `mermaid_create*` call, threaded from the
43+ /// dispatch context so each diagram records what generated it.
44+ #[ derive( Clone , Default ) ]
45+ pub struct DiagramOrigin {
46+ pub provider : Option < String > ,
47+ pub model : Option < String > ,
3448}
3549
3650#[ derive( Serialize ) ]
@@ -40,13 +54,13 @@ struct DiagramsEnvelope {
4054
4155/// Result of building/persisting a set of diagrams: a JSON string ready for the
4256/// tool `content`, or an error message.
43- pub fn run_create ( ws : & str , args : & Value ) -> Result < String , String > {
57+ pub fn run_create ( ws : & str , args : & Value , origin : & DiagramOrigin ) -> Result < String , String > {
4458 let one = parse_one ( args) ?;
45- let out = build_one ( ws, one) ?;
59+ let out = build_one ( ws, one, origin ) ?;
4660 finish ( vec ! [ out] )
4761}
4862
49- pub fn run_create_many ( ws : & str , args : & Value ) -> Result < String , String > {
63+ pub fn run_create_many ( ws : & str , args : & Value , origin : & DiagramOrigin ) -> Result < String , String > {
5064 let plan_slug = opt_str ( args, "plan_slug" ) ;
5165 let items = args
5266 . get ( "diagrams" )
@@ -65,7 +79,7 @@ pub fn run_create_many(ws: &str, args: &Value) -> Result<String, String> {
6579 if spec. plan_slug . is_none ( ) {
6680 spec. plan_slug = plan_slug. clone ( ) ;
6781 }
68- out. push ( build_one ( ws, spec) ?) ;
82+ out. push ( build_one ( ws, spec, origin ) ?) ;
6983 }
7084 finish ( out)
7185}
@@ -98,7 +112,7 @@ fn parse_one(v: &Value) -> Result<Spec, String> {
98112 } )
99113}
100114
101- fn build_one ( ws : & str , spec : Spec ) -> Result < DiagramOut , String > {
115+ fn build_one ( ws : & str , spec : Spec , origin : & DiagramOrigin ) -> Result < DiagramOut , String > {
102116 match & spec. plan_slug {
103117 Some ( slug) => {
104118 let rec = store:: create_diagram (
@@ -109,6 +123,8 @@ fn build_one(ws: &str, spec: Spec) -> Result<DiagramOut, String> {
109123 & spec. kind ,
110124 spec. task_id . clone ( ) ,
111125 spec. id ,
126+ origin. provider . clone ( ) ,
127+ origin. model . clone ( ) ,
112128 ) ?;
113129 Ok ( DiagramOut {
114130 id : rec. meta . id ,
@@ -118,6 +134,8 @@ fn build_one(ws: &str, spec: Spec) -> Result<DiagramOut, String> {
118134 task_id : rec. meta . task_id ,
119135 plan_slug : Some ( slug. clone ( ) ) ,
120136 persisted : true ,
137+ provider : rec. meta . provider ,
138+ model : rec. meta . model ,
121139 } )
122140 }
123141 None => Ok ( DiagramOut {
@@ -128,6 +146,8 @@ fn build_one(ws: &str, spec: Spec) -> Result<DiagramOut, String> {
128146 task_id : spec. task_id ,
129147 plan_slug : None ,
130148 persisted : false ,
149+ provider : origin. provider . clone ( ) ,
150+ model : origin. model . clone ( ) ,
131151 } ) ,
132152 }
133153}
@@ -174,25 +194,39 @@ mod tests {
174194
175195 #[ test]
176196 fn ephemeral_when_no_plan_slug ( ) {
197+ let origin = DiagramOrigin {
198+ provider : Some ( "openrouter" . into ( ) ) ,
199+ model : Some ( "openai/gpt-5" . into ( ) ) ,
200+ } ;
177201 let out = run_create (
178202 "/nonexistent-ws-ignored" ,
179203 & json ! ( { "title" : "Flow" , "code" : "flowchart TD\n A-->B" } ) ,
204+ & origin,
180205 )
181206 . unwrap ( ) ;
182207 assert ! ( out. contains( "\" persisted\" :false" ) ) ;
183208 assert ! ( out. contains( "\" id\" :\" flow\" " ) ) ;
209+ // Origin is stamped onto ephemeral diagrams too.
210+ assert ! ( out. contains( "\" provider\" :\" openrouter\" " ) ) ;
211+ assert ! ( out. contains( "\" model\" :\" openai/gpt-5\" " ) ) ;
184212 }
185213
186214 #[ test]
187215 fn rejects_oversize_code ( ) {
188216 let big = "x" . repeat ( MAX_CODE_BYTES + 1 ) ;
189- let err = run_create ( "/ws" , & json ! ( { "title" : "T" , "code" : big } ) ) . unwrap_err ( ) ;
217+ let err = run_create (
218+ "/ws" ,
219+ & json ! ( { "title" : "T" , "code" : big } ) ,
220+ & DiagramOrigin :: default ( ) ,
221+ )
222+ . unwrap_err ( ) ;
190223 assert ! ( err. contains( "byte limit" ) ) ;
191224 }
192225
193226 #[ test]
194227 fn many_rejects_empty ( ) {
195- let err = run_create_many ( "/ws" , & json ! ( { "diagrams" : [ ] } ) ) . unwrap_err ( ) ;
228+ let err = run_create_many ( "/ws" , & json ! ( { "diagrams" : [ ] } ) , & DiagramOrigin :: default ( ) )
229+ . unwrap_err ( ) ;
196230 assert ! ( err. contains( "empty" ) ) ;
197231 }
198232}
0 commit comments