@@ -22,12 +22,7 @@ import { Pool } from "pg";
2222
2323import { MetaDataLoader } from "@metaobjectsdev/metadata" ;
2424import { buildExpectedSchema , diff , emit } from "@metaobjectsdev/migrate-ts" ;
25- import {
26- Format ,
27- LlmCallDbRecorder ,
28- ObjectManager ,
29- recordLlmCall ,
30- } from "@metaobjectsdev/runtime-ts" ;
25+ import { buildLlmCallRow , ObjectManager } from "@metaobjectsdev/runtime-ts" ;
3126import { kyselyDriver } from "@metaobjectsdev/runtime-ts/drivers" ;
3227
3328import { startPostgres } from "../src/postgres-container.ts" ;
@@ -65,6 +60,9 @@ const META = JSON.stringify({
6560 { "field.uuid" : { name : "parentSpanId" } } ,
6661 { "field.string" : { name : "sessionId" } } ,
6762 { "field.string" : { name : "callType" } } ,
63+ // `system` (gen_ai.system) is part of the 18-field base row that
64+ // buildLlmCallRow writes; the hand-rolled TPH base must declare it.
65+ { "field.string" : { name : "system" } } ,
6866 { "field.string" : { name : "requestModel" } } ,
6967 { "field.string" : { name : "responseModel" } } ,
7068 { "field.int" : { name : "inputTokens" } } ,
@@ -76,6 +74,7 @@ const META = JSON.stringify({
7674 { "field.string" : { name : "errorDetail" } } ,
7775 { "field.string" : { name : "startedAt" } } ,
7876 { "field.string" : { name : "llmRequest" , "@dbColumnType" : "jsonb" } } ,
77+ { "field.string" : { name : "llmResponse" , "@dbColumnType" : "jsonb" } } ,
7978 { "identity.primary" : { "@fields" : "spanId" } } ,
8079 ] ,
8180 } ,
@@ -114,8 +113,6 @@ describe("ai-trace #1c — shared-table persistence (real Postgres)", () => {
114113 const result = await MetaDataLoader . fromString ( META , "json" ) ;
115114 expect ( result . errors ) . toHaveLength ( 0 ) ;
116115 const root = result . root ;
117- const classifyRes = root . findObject ( "ClassifyRes" ) ! ;
118- const summarizeRes = root . findObject ( "SummarizeRes" ) ! ;
119116
120117 // ONE-TABLE assertion: the STI subtypes do NOT each get their own table —
121118 // exactly one prompt_llm_call table is synthesised for the whole hierarchy.
@@ -137,28 +134,35 @@ describe("ai-trace #1c — shared-table persistence (real Postgres)", () => {
137134 const driver = kyselyDriver ( { db : kysely as never , dialect : "postgres" } ) ;
138135 const om = new ObjectManager ( { metadata : root , driver, columnNamingStrategy : "literal" } ) ;
139136
140- await recordLlmCall (
141- {
137+ // The generic recorder writes only the base envelope + raw I/O; the typed
138+ // voResponse is supplied by the generated typed helper. Here we model that
139+ // helper's output directly: buildLlmCallRow(base) spread with the typed VO.
140+ await om . create ( "ClassifyCall" , {
141+ ...buildLlmCallRow ( {
142142 spanId : C_SPAN ,
143143 traceId : C_TRACE ,
144144 callType : "classify" ,
145145 startedAt : "2026-06-05T00:00:00.000Z" ,
146146 llmRequest : { text : "hi" } ,
147147 llmResponseText : '{"label":"greeting","score":1}' ,
148- } ,
149- { recorder : new LlmCallDbRecorder ( om , "ClassifyCall" ) , responseMo : classifyRes , format : Format . JSON } ,
150- ) ;
151- await recordLlmCall (
152- {
148+ status : "ok" ,
149+ errorDetail : null ,
150+ } ) ,
151+ voResponse : { label : "greeting" , score : 1 } ,
152+ } ) ;
153+ await om . create ( "SummarizeCall" , {
154+ ...buildLlmCallRow ( {
153155 spanId : S_SPAN ,
154156 traceId : S_TRACE ,
155157 callType : "summarize" ,
156158 startedAt : "2026-06-05T00:00:00.000Z" ,
157159 llmRequest : { doc : "..." } ,
158160 llmResponseText : '{"summary":"short"}' ,
159- } ,
160- { recorder : new LlmCallDbRecorder ( om , "SummarizeCall" ) , responseMo : summarizeRes , format : Format . JSON } ,
161- ) ;
161+ status : "ok" ,
162+ errorDetail : null ,
163+ } ) ,
164+ voResponse : { summary : "short" } ,
165+ } ) ;
162166
163167 const c = await om . findById ( "ClassifyCall" , C_SPAN ) as Record < string , unknown > ;
164168 expect ( c . callType ) . toBe ( "classify" ) ;
0 commit comments