@lqc123qwe/car-runtime 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/SHA256SUMS CHANGED
@@ -1 +1 @@
1
- 49d18ea2a3d001d3c67c53bd63e422393a944fb4dece4f52c709c50ade2c40be car-runtime-1.19.0.tgz
1
+ 8d9bd1fa4d447b255ae668a51bb47338bde8e35ad8ccac97ac72d2c893664115 car-runtime-1.21.0.tgz
Binary file
@@ -0,0 +1 @@
1
+ {"base64Signature":"MEQCIEr84YFIo6UQdCBqDYqzEn5iofitA9Wtuxx9AqUbuNGZAiBdNvJTODyumJazgvvJunU8HC/jVNGcHjUM9u7quHIyOg==","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1ekNDQm0yZ0F3SUJBZ0lVYWV0eWY3L29XeUE3RlVhb1RuV0k0QjYzVU1Vd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1URTBNRE16TlRNM1doY05Nall4TVRFME1ETTBOVE0zV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpdlNPYnBEY1ZHZjd3Vm0yTWhUajlqbTdmQ0Z2ZGFZeGYrVTcKS2ljbzJLbUdNdWRXRUk4KzJuYmxtQmN6Q0tYNWoyWGMrNDRHTTk0bW5EUkF3OTdZT0tPQ0JZd3dnZ1dJTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl4TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLRFZsWW1Fd1pHTXpNRGhpT1dWak1qSTVNR1l4Tm1GbApabUV5TVdNNVlUVXlPR1UzTkRReU9EQXdGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNakV1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakl4TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENnMQpaV0poTUdSak16QTRZamxsWXpJeU9UQm1NVFpoWldaaE1qRmpPV0UxTWpobE56UTBNamd3TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENnMQpaV0poTUdSak16QTRZamxsWXpJeU9UQm1NVFpoWldaaE1qRmpPV0UxTWpobE56UTBNamd3TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNakV1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpJeExqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2cxWldKaE1HUmpNekE0WWpsbFl6SXlPVEJtTVRaaFpXWmhNakZqT1dFMU1qaGxOelEwTWpnd01CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXpOVFV5TkRFMwpPVFE0TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eU1TNHdNSUdLQmdvckJnRUVBZFo1QWdRQ0JId0VlZ0I0QUhZQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2gyQi85K0FBQUJBTUFSekJGCkFpRUE2QlpwRnVTK3Y5WVVqZXNxWk02WGFyTXcydDVNbmUxaldSOStkYXVJQ0dJQ0lBSWhPL2h4b0NUQitCdlgKbkVRRmlmNzZvanVtbHlLeWYvckF3V1Rzc3VPMk1Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01HK2R4dS9BV2tMOQo4QUtiSlhXSXYzMFlGNjk5UGp5cWFDcVhTSUllY25VRzdPZHFXTzRYK2dwb1RNVEc1bXRJcVFJeEFOT0xZRGFZCndTNUhoU0RNcXVLU1M2ODRjL1U2bTlLc0syWmN2RGFrRlZOL1BiZHhCc1Y5cE1ySzZFeEVMblpEb3c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEUCIB04W7cJqnNa2clZD6iGQQPWdN+4NyyrSfjpxuDJ5cWIAiEAlr/UsHUN8IVr0S/5WeyxAYNKIpQUnHcPyToMjqgz+HA=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4ZDliZDFmYTRkNDQ3YjI1NWFlNjY4YTUxYmI0NzMzOGJkZThlMzVhZDhjY2FjOTdhYzcyZDJjODkzNjY0MTE1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRXI4NFlGSW82VVFkQ0JxRFlxekVuNWlvZml0QTlXdHV4eDlBcVVidU5HWkFpQmROdkpUT0R5dW1KYXpndnZKdW5VOEhDL2pWTkdjSGpVTTl1N3F1SEl5T2c9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFla05EUW0weVowRjNTVUpCWjBsVllXVjBlV1kzTDI5WGVVRTNSbFZoYjFSdVYwazBRall6VlUxVmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUlRCTlJFMTZUbFJOTTFkb1kwNU5hbGw0VFZSRk1FMUVUVEJPVkUwelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwZGxOUFluQkVZMVpIWmpkM1ZtMHlUV2hVYWpscWJUZG1RMFoyWkdGWmVHWXJWVGNLUzJsamJ6SkxiVWROZFdSWFJVazRLekp1WW14dFFtTjZRMHRZTldveVdHTXJORFJIVFRrMGJXNUVVa0YzT1RkWlQwdFBRMEpaZDNkbloxZEpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha2w0VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUkZac1dXMUZkMXBIVFhwTlJHaHBUMWRXYWsxcVNUVk5SMWw0VG0xR2JBcGFiVVY1VFZkTk5WbFVWWGxQUjFVelRrUlJlVTlFUVhkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOYWtWMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrbDRUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm5NUXBhVjBwb1RVZFNhazE2UVRSWmFteHNXWHBKZVU5VVFtMU5WRnBvV2xkYWFFMXFSbXBQVjBVeFRXcG9iRTU2VVRCTmFtZDNUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVObk1RcGFWMHBvVFVkU2FrMTZRVFJaYW14c1dYcEplVTlVUW0xTlZGcG9XbGRhYUUxcVJtcFBWMFV4VFdwb2JFNTZVVEJOYW1kM1RVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOYWtWMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BKZUV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJjeFdsZEthRTFIVW1wTmVrRTBXV3BzYkZsNlNYbFBWRUp0VFZSYWFGcFhXbWhOYWtacVQxZEZNVTFxYUd4T2VsRXdUV3BuZDAxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYcE9WRlY1VGtSRk13cFBWRkUwVERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZVUxVE5IZE5TVWRMUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJZDBWbFowSTBRVWhaUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyZ3lRaTg1SzBGQlFVSkJUVUZTZWtKR0NrRnBSVUUyUWxwd1JuVlRLM1k1V1ZWcVpYTnhXazAyV0dGeVRYY3lkRFZOYm1VeGFsZFNPU3RrWVhWSlEwZEpRMGxCU1doUEwyaDRiME5VUWl0Q2RsZ0tia1ZSUm1sbU56WnZhblZ0YkhsTGVXWXZja0YzVjFSemMzVlBNazFCYjBkRFEzRkhVMDAwT1VKQlRVUkJNbWRCVFVkVlEwMUhLMlI0ZFM5QlYydE1PUW80UVV0aVNsaFhTWFl6TUZsR05qazVVR3A1Y1dGRGNWaFRTVWxsWTI1VlJ6ZFBaSEZYVHpSWUsyZHdiMVJOVkVjMWJYUkpjVkZKZUVGT1QweFpSR0ZaQ25kVE5VaG9VMFJOY1hWTFUxTTJPRFJqTDFVMmJUbExjMHN5V21OMlJHRnJSbFpPTDFCaVpIaENjMVk1Y0UxeVN6WkZlRVZNYmxwRWIzYzlQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1794627338,"logIndex":3868826566,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
package/dist/cli.js CHANGED
@@ -223,6 +223,17 @@ async function main() {
223
223
  // 池/单端点两分支通用,须先于适配器创建)
224
224
  if (rest.includes('--no-prompt-cache'))
225
225
  process.env.CAR_LLM_PROMPT_CACHE = 'off';
226
+ // 1.20-K1(D-48 P2):滚动断点白名单——flag > env > config(env = 适配器每请求求值的
227
+ // 运行时事实源;'off' = 显式清空;池成员身份在 pool 装配处按 m.id 传入,不走本 env 的单端点语义)
228
+ const rollingFlag = flagValue(rest, '--prompt-cache-rolling');
229
+ if (rollingFlag !== undefined)
230
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = rollingFlag;
231
+ else if (!process.env.CAR_LLM_PROMPT_CACHE_ROLLING && llm.promptCache?.rolling?.adapters?.length) {
232
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = llm.promptCache.rolling.adapters.join(',');
233
+ }
234
+ if (llm.promptCache?.rolling?.breakpoints !== undefined) {
235
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS = String(llm.promptCache.rolling.breakpoints);
236
+ }
226
237
  const credService = new CredentialService();
227
238
  const core = new RuntimeCore(credService);
228
239
  core.bindContext(ctx);
package/dist/dx/doctor.js CHANGED
@@ -87,12 +87,40 @@ export function doctorModelReadiness(opts = {}) {
87
87
  catch { /* 全落空 = not-found(登记口径,非错误) */ }
88
88
  }
89
89
  const configured = !!baseUrl && !!model;
90
+ // 1.20-K1(D-48 P2):提示缓存策略可视(断点 = 传输层注解;rolling = 白名单缺省关闭,
91
+ // 登记前置 = 该端点 rolling 形态探针 VERDICT=A——1.20 设计 §3.5 流程门控)
92
+ const cacheOff = env.CAR_LLM_PROMPT_CACHE === 'off';
93
+ const rollingEnv = env.CAR_LLM_PROMPT_CACHE_ROLLING;
94
+ const cfgRolling = cfg.error ? undefined : cfg.config.llm?.promptCache?.rolling;
95
+ const rollingList = rollingEnv !== undefined
96
+ ? rollingEnv.split(',').map(s => s.trim()).filter(Boolean)
97
+ : (cfgRolling?.adapters ?? []);
98
+ const rollingShown = rollingEnv !== undefined
99
+ ? rollingList
100
+ : rollingList.length ? rollingList : undefined;
101
+ // 白名单未匹配告警(fail-visible 不阻断——成员可能已删,但登记错误必须可见)
102
+ const knownIdentities = new Set();
103
+ if (!cfg.error && cfg.config.modelPool?.endpoints.length) {
104
+ cfg.config.modelPool.endpoints.forEach((ep, i) => knownIdentities.add(ep.id ?? `ep-${i + 1}`));
105
+ }
106
+ else if (adapterId === 'anthropic') {
107
+ knownIdentities.add('anthropic');
108
+ }
109
+ const unmatched = rollingList.filter(id => !knownIdentities.has(id));
110
+ const cacheParts = [
111
+ `promptCache=${cacheOff ? 'off' : 'on'}`,
112
+ rollingShown === undefined ? 'rolling=缺省关闭(白名单须探针 VERDICT=A 后登记——llm.promptCache.rolling.adapters / CAR_LLM_PROMPT_CACHE_ROLLING)'
113
+ : rollingShown.includes('off') ? 'rolling=off(显式清空)'
114
+ : `rolling=[${rollingShown.join(',')}]${cfgRolling?.breakpoints !== undefined ? ` breakpoints=${cfgRolling.breakpoints}` : ''}`,
115
+ unmatched.length ? `⚠️ 白名单含未匹配身份:${unmatched.join(',')}(无在册单端点 anthropic / 池成员 id 对应——登记错误或成员已删)` : '',
116
+ ].filter(Boolean);
90
117
  const parts = [
91
118
  configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
92
119
  credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
93
120
  : credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
94
121
  : '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
95
122
  `envFallback=${envFallback}`,
123
+ ...cacheParts,
96
124
  ];
97
125
  return { configured, baseUrl: baseUrl ?? '', model: model ?? '', credential, envFallback, detail: parts.join(';') };
98
126
  }
@@ -25,6 +25,17 @@ export interface LlmConfig {
25
25
  compaction?: CompactionConfig;
26
26
  /** 1.16-K3(D-K2-2):子 Agent 配置(缺省 = 继承父会话模型锚点 Q-A2 + 宿主护栏常量 Q-A3) */
27
27
  subagent?: SubagentConfig;
28
+ /** 1.20-K1(D-48 P2):提示缓存滚动断点白名单(缺省关闭;运行时事实源 = CAR_LLM_PROMPT_CACHE_ROLLING,cli 按 flag > env > config 落 env) */
29
+ promptCache?: PromptCacheConfig;
30
+ }
31
+ /** 1.20-K1(D-48 P2):滚动断点白名单(1.20 设计 §3.4/§3.5——逐条确证,无通配) */
32
+ export interface PromptCacheConfig {
33
+ rolling?: {
34
+ /** 路由身份白名单(单端点 'anthropic' / 池成员 id);空表显式拒绝(禁静默 no-op);登记前置 = 该端点探针 VERDICT=A */
35
+ adapters?: string[];
36
+ /** 滚动断点数(1..3 钳制;缺省 1——多挂对同一前缀重复写 1.25x 缓存,1.20 设计 §3.3) */
37
+ breakpoints?: number;
38
+ };
28
39
  }
29
40
  /** 1.16-K3(D-K2-2):子 Agent 配置面——宿主编排消费(内核自身不装配编排;schema 只登记键位) */
30
41
  export interface SubagentConfig {
@@ -148,6 +148,17 @@ const rawSchema = z.object({
148
148
  }).strict()
149
149
  .refine(v => Object.keys(v).length > 0, { message: '至少登记一个键(model/maxConcurrent/maxCumulative;无子 Agent 配置需求请移除 llm.subagent 键,行为与 1.15.2 一致)' })
150
150
  .optional(),
151
+ // 1.20-K1(D-48 P2):滚动断点白名单(路由身份精确匹配;adapters 空表显式拒——禁静默 no-op)
152
+ promptCache: z.object({
153
+ rolling: z.object({
154
+ adapters: z.array(nonEmptyTrimmed(), { invalid_type_error: '必须为字符串数组' })
155
+ .min(1, { message: '至少登记一个路由身份(空表 = 静默 no-op 显式拒绝;不需要滚动断点请移除 llm.promptCache 键)' })
156
+ .optional(),
157
+ breakpoints: positiveInt()
158
+ .refine(v => v <= 3, { message: '必须为 1..3(provider 断点硬限 4,system 占 1)' })
159
+ .optional(),
160
+ }).strict().optional(),
161
+ }).strict().optional(),
151
162
  }).strict().optional(),
152
163
  // 1.14-S4(K-POOL-1 / D-42):模型池登记(多端点 fallback + health + routing/decide 落链)
153
164
  modelPool: modelPoolSchema.optional(),
@@ -221,6 +232,18 @@ function parseCarConfig(raw) {
221
232
  sg.maxCumulative = d.llm.subagent.maxCumulative;
222
233
  llm.subagent = sg;
223
234
  }
235
+ if (d.llm.promptCache) {
236
+ const pc = {};
237
+ if (d.llm.promptCache.rolling) {
238
+ const r = {};
239
+ if (d.llm.promptCache.rolling.adapters !== undefined)
240
+ r.adapters = [...d.llm.promptCache.rolling.adapters];
241
+ if (d.llm.promptCache.rolling.breakpoints !== undefined)
242
+ r.breakpoints = d.llm.promptCache.rolling.breakpoints;
243
+ pc.rolling = r;
244
+ }
245
+ llm.promptCache = pc;
246
+ }
224
247
  out.llm = llm;
225
248
  }
226
249
  if (d.mcp) {
@@ -372,6 +395,8 @@ export function mergeLlmConfig(env, cfg, flags = {}) {
372
395
  ...(adapterId ? { adapterId } : {}),
373
396
  ...(maxTokens !== undefined ? { maxTokens } : {}),
374
397
  allowEnvFallback,
398
+ // 1.20-K1(D-48 P2):滚动断点声明层透传(cli 层按 flag > env > config 落运行时 env)
399
+ ...(cfg.llm?.promptCache ? { promptCache: cfg.llm.promptCache } : {}),
375
400
  };
376
401
  }
377
402
  /**
package/dist/load/pool.js CHANGED
@@ -226,6 +226,9 @@ export function assembleModelPool(opts) {
226
226
  ? createAnthropicAdapter({
227
227
  baseUrl: m.baseUrl,
228
228
  credentials: opts.credentials,
229
+ // 1.20-K1(D-48 P2):滚动断点按成员路由身份求值——池成员适配器 id 恒 'anthropic'
230
+ //(装配未透传),禁按其求值(一端点解锁 = 全池解锁,1.20 设计 §3.1)
231
+ rollingIdentity: m.id,
229
232
  ...(m.keyEnv !== undefined ? { credentialEnvVars: [m.keyEnv] } : {}),
230
233
  ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
231
234
  ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
@@ -1,4 +1,4 @@
1
- {"stage":"archive","detail":"git archive HEAD → car-runtime-1.19.0.tgz(421089 bytes,树哈希锚定)","ts":1793526334104}
2
- {"stage":"sha256sums","detail":"SHA256SUMS 49d18ea2a3d001d3…(裸字节复核通过)","ts":1793526334105}
3
- {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1793526334112}
4
- {"stage":"cosign-keyless","detail":"signed car-runtime-1.19.0.tgz (keyless OIDC + Rekor)","ts":1793526339914}
1
+ {"stage":"archive","detail":"git archive HEAD → car-runtime-1.21.0.tgz(436516 bytes,树哈希锚定)","ts":1794627336025}
2
+ {"stage":"sha256sums","detail":"SHA256SUMS 8d9bd1fa4d447b25…(裸字节复核通过)","ts":1794627336026}
3
+ {"stage":"sbom","detail":"55 个组件(运行时依赖 0 项)","ts":1794627336033}
4
+ {"stage":"cosign-keyless","detail":"signed car-runtime-1.21.0.tgz (keyless OIDC + Rekor)","ts":1794627338842}
@@ -21,13 +21,25 @@ export async function chatStep(opts) {
21
21
  const messages = log.deriveMessages(undefined, { activeTurnId: turnId }).map(m => ({
22
22
  role: m.role,
23
23
  content: m.toolCall !== undefined
24
- ? { toolCall: m.toolCall }
24
+ ? {
25
+ toolCall: m.toolCall,
26
+ // 1.21(R-TOKENHUB-1 / D-T3/D-T4):投影层附加的同 turn thinking 三元组随 toolCall
27
+ // 载荷透传(适配器回传消费——toAnthropic 前置 thinking 块;openai 形忽略该键)
28
+ ...(m.thinking !== undefined
29
+ ? { thinking: m.thinking }
30
+ : {}),
31
+ }
25
32
  : m.content,
26
33
  }));
27
34
  // 4. 流式消费(3. 凭据解析在适配器首块前,A080001 由此传播)
28
35
  const redactor = new StreamRedactor();
29
36
  let text = '';
30
37
  const calls = new Map();
38
+ // 1.21(R-TOKENHUB-1 / D-T1/D-T2):thinking 捕获累积——禁改写(回传签名完整性 > 脱敏
39
+ // 改写),provider 事实字节保真;redacted_thinking = data 全文单发覆盖
40
+ let thinkingText = '';
41
+ let thinkingSignature = '';
42
+ let thinkingRedacted;
31
43
  let stopReason;
32
44
  let errorDetail;
33
45
  let usage;
@@ -67,6 +79,13 @@ export async function chatStep(opts) {
67
79
  cur.argsBuf += chunk.toolCallDelta.argumentsDelta;
68
80
  calls.set(idx, cur);
69
81
  }
82
+ // 1.21(R-TOKENHUB-1 / D-T1):thinking 三面捕获(text/signature 增量累积、redacted 单发覆盖)
83
+ if (chunk.thinkingDelta !== undefined)
84
+ thinkingText += chunk.thinkingDelta;
85
+ if (chunk.thinkingSignatureDelta !== undefined)
86
+ thinkingSignature += chunk.thinkingSignatureDelta;
87
+ if (chunk.thinkingRedacted !== undefined)
88
+ thinkingRedacted = chunk.thinkingRedacted;
70
89
  if (chunk.error)
71
90
  errorDetail = chunk.error.message;
72
91
  if (chunk.usage)
@@ -103,6 +122,20 @@ export async function chatStep(opts) {
103
122
  providerRaw: usage.providerRaw,
104
123
  });
105
124
  }
125
+ // 5a. 1.21(R-TOKENHUB-1 / D-T2):thinking 事件落链(actor='model',先于 assistant/toolCall
126
+ // ——投影层按同 turn 首 toolCall 附加回传)。payload = thinking 原文禁改写(签名完整性 >
127
+ // 脱敏改写——改写即回传校验失败);secretsRedacted 仅计数留痕(审计信号,不复写);
128
+ // redacted 形态 payload = data 全文(meta.redacted=true)。无 thinking chunk = 零新事件
129
+ // (D-T5 字节兼容,链形态与 1.20 全等)。
130
+ if (thinkingText.length || thinkingRedacted !== undefined) {
131
+ const thinkingPayload = thinkingRedacted ?? thinkingText;
132
+ const secretsInThinking = scanSecrets(thinkingPayload).hits.length;
133
+ log.append('model', 'thinking', turnId, thinkingPayload, {
134
+ ...(thinkingRedacted !== undefined ? { redacted: true } : {}),
135
+ ...(thinkingSignature ? { signature: thinkingSignature } : {}),
136
+ ...(secretsInThinking ? { secretsRedacted: secretsInThinking } : {}),
137
+ });
138
+ }
106
139
  // 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
107
140
  // flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
108
141
  let secretsRedacted = 0;
@@ -76,7 +76,21 @@ export interface AnthropicAdapterOptions {
76
76
  defaultMaxTokens?: number;
77
77
  /** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
78
78
  credentialEnvVars?: string[];
79
+ /**
80
+ * 1.20-K1(D-48 P2):滚动断点求值身份。缺省 = id(单端点 'anthropic');
81
+ * **池成员必须显式传 m.id**——池成员适配器 id 恒 'anthropic'(装配未透传成员标识),
82
+ * 按其求值 = 白名单一端点解锁全池(1.20 设计 §3.1 专项锁,S66/AC-9)。
83
+ */
84
+ rollingIdentity?: string;
79
85
  }
86
+ /**
87
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
88
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
89
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
90
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
91
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
92
+ */
93
+ export declare function rollingCacheAllowed(identity: string): boolean;
80
94
  export declare function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapter & {
81
95
  id: string;
82
96
  };
@@ -535,11 +535,68 @@ function mergeAnthropicUsage(start, delta, model, adapterId) {
535
535
  * 投影/快照比对,N1 零影响;生效有 provider 侧最小长度门槛(~1024 tokens)——短 system
536
536
  * 静默不生效应以 usage.cacheCreationTokens 观测,禁以「配了就该生效」推断。关断 =
537
537
  * CAR_LLM_PROMPT_CACHE=off(cli flag --no-prompt-cache 置本 env;A/B 与故障回退通道)。
538
+ * 1.20-K1(D-48 P2):滚动断点——白名单命中时在尾部消息挂第二处断点(缺省 1 个,只块化命中
539
+ * 消息),键 = 路由身份(rollingIdentity,池成员 = m.id);CAR_LLM_PROMPT_CACHE_ROLLING
540
+ * 逗号清单('off' = 显式清空,无通配)+ CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS(1..3 钳制)。
538
541
  */
539
542
  function promptCacheOff() {
540
543
  return process.env.CAR_LLM_PROMPT_CACHE === 'off';
541
544
  }
542
- function toAnthropic(req, apiKey) {
545
+ /**
546
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
547
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
548
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
549
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
550
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
551
+ */
552
+ export function rollingCacheAllowed(identity) {
553
+ if (promptCacheOff())
554
+ return false;
555
+ const raw = process.env.CAR_LLM_PROMPT_CACHE_ROLLING;
556
+ if (!raw)
557
+ return false;
558
+ const list = raw.split(',').map(s => s.trim()).filter(Boolean);
559
+ if (!list.length || list.includes('off'))
560
+ return false;
561
+ return list.includes(identity);
562
+ }
563
+ // 1.20-K1:滚动断点数——provider 硬限 4,system 断点(P1)恒占 1;缺省 1(多挂 = 对同一前缀
564
+ // 重复写 1.25x 缓存,1.20 设计 §3.3)。env 非法/缺席回落缺省,超限钳制(AC-8)
565
+ const ROLLING_BREAKPOINTS_DEFAULT = 1;
566
+ const ROLLING_BREAKPOINTS_MAX = 3;
567
+ function rollingBreakpoints() {
568
+ const raw = Number(process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS);
569
+ if (!Number.isFinite(raw) || raw < 1)
570
+ return ROLLING_BREAKPOINTS_DEFAULT;
571
+ return Math.min(Math.trunc(raw), ROLLING_BREAKPOINTS_MAX);
572
+ }
573
+ /**
574
+ * 1.20-K1(D-48 P2):滚动断点放置——从尾部往前命中即挂,只块化被挂消息,其余消息维持
575
+ * 字符串(1.20 设计 §3.2:全量块化零收益高风险)。断点挂**末块**(tool_result / tool_use
576
+ * 多块形态不拆不重组,语义不变);空串 / 空数组 / 非对象末块无可挂载体 → 跳过前移(不换位不补造)。
577
+ * 纯传输层注解:只改本请求出站体,不进 SessionLog / 投影(N1 零影响,S59 同红线)。
578
+ */
579
+ function attachRollingBreakpoints(turns, count) {
580
+ let attached = 0;
581
+ for (let i = turns.length - 1; i >= 0 && attached < count; i--) {
582
+ const t = turns[i];
583
+ if (typeof t.content === 'string') {
584
+ if (!t.content.length)
585
+ continue;
586
+ t.content = [{ type: 'text', text: t.content, cache_control: { type: 'ephemeral' } }];
587
+ attached++;
588
+ continue;
589
+ }
590
+ if (Array.isArray(t.content) && t.content.length) {
591
+ const last = t.content[t.content.length - 1];
592
+ if (last && typeof last === 'object' && !Array.isArray(last)) {
593
+ last.cache_control = { type: 'ephemeral' };
594
+ attached++;
595
+ }
596
+ }
597
+ }
598
+ }
599
+ function toAnthropic(req, apiKey, cache) {
543
600
  const system = [];
544
601
  const turns = [];
545
602
  let pendingToolResults = [];
@@ -567,13 +624,27 @@ function toAnthropic(req, apiKey) {
567
624
  flushToolResults();
568
625
  if (m.role === 'assistant' && m.content != null && typeof m.content === 'object' && 'toolCall' in m.content) {
569
626
  const tc = m.content.toolCall;
570
- turns.push({ role: 'assistant', content: [{ type: 'tool_use', id: tc.id, name: tc.tool, input: tc.args ?? {} }] });
627
+ // 1.21(R-TOKENHUB-1 / D-T4):工具环回传——投影层附加的 thinking(同 turn 原始 assistant
628
+ // 消息的 thinking 块)前置重放(Anthropic 规范:thinking 须位于同消息 text/tool_use 之前;
629
+ // tokenhub 等网关对缺回传 400——R-TOKENHUB-1 实证)。无 thinking = 形态与 1.20 逐字节一致
630
+ // (D-T5 兼容线,golden 不动)。
631
+ const th = m.content.thinking;
632
+ const thinkingBlock = th?.text?.length
633
+ ? th.redacted
634
+ ? { type: 'redacted_thinking', data: th.text }
635
+ : { type: 'thinking', thinking: th.text, ...(th.signature ? { signature: th.signature } : {}) }
636
+ : undefined;
637
+ turns.push({ role: 'assistant', content: [...(thinkingBlock ? [thinkingBlock] : []), { type: 'tool_use', id: tc.id, name: tc.tool, input: tc.args ?? {} }] });
571
638
  }
572
639
  else {
573
640
  turns.push({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) });
574
641
  }
575
642
  }
576
643
  flushToolResults();
644
+ // 1.20-K1(D-48 P2):滚动断点在白名单命中时挂尾部消息(缺省 1 个)——system 断点(P1)
645
+ // 与消息断点独立,off 时两者俱灭(rollingCacheAllowed 内已连坐)
646
+ if (cache.rolling)
647
+ attachRollingBreakpoints(turns, cache.rollingBreakpoints);
577
648
  // 1.14-S1(K-MP-1 / D-39):思考参数——effort → thinking.budget_tokens(ANTHROPIC_THINKING_BUDGET 表);
578
649
  // off/缺席不下发;budget ≥ max_tokens 违反 Messages API 约束(max_tokens 必须大于 budget_tokens)→
579
650
  // 显式报错提示调高(fail-visible,禁静默改写 max_tokens)
@@ -645,6 +716,10 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
645
716
  if (block?.type === 'tool_use') {
646
717
  yield { toolCallDelta: { index: evt.index, id: block.id, name: block.name } };
647
718
  }
719
+ else if (block?.type === 'redacted_thinking' && typeof block.data === 'string' && block.data.length) {
720
+ // 1.21(R-TOKENHUB-1 / D-T1):redacted_thinking 全文单发(回传形态 {type:'redacted_thinking',data})
721
+ yield { thinkingRedacted: block.data };
722
+ }
648
723
  }
649
724
  else if (type === 'content_block_delta') {
650
725
  const d = evt.delta;
@@ -654,7 +729,14 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
654
729
  else if (d?.type === 'input_json_delta' && typeof d.partial_json === 'string' && d.partial_json.length) {
655
730
  yield { toolCallDelta: { index: evt.index, argumentsDelta: d.partial_json } };
656
731
  }
657
- // thinking_delta / citations_delta 等非 CAR 消费面:容错跳过
732
+ else if (d?.type === 'thinking_delta' && typeof d.thinking === 'string' && d.thinking.length) {
733
+ // 1.21(R-TOKENHUB-1 / D-T1):thinking 文本增量捕获(原容错跳过 → 落链 + 工具环回传)
734
+ yield { thinkingDelta: d.thinking };
735
+ }
736
+ else if (d?.type === 'signature_delta' && typeof d.signature === 'string' && d.signature.length) {
737
+ yield { thinkingSignatureDelta: d.signature };
738
+ }
739
+ // citations_delta 等其余非 CAR 消费面:容错跳过
658
740
  }
659
741
  else if (type === 'message_start') {
660
742
  // 1.11-S1(D-33):input 侧采集(usage 嵌套在 message 下)
@@ -687,6 +769,8 @@ async function* parseAnthropicSseStream(body, model, adapterId) {
687
769
  }
688
770
  export function createAnthropicAdapter(opts) {
689
771
  const id = opts.id ?? 'anthropic';
772
+ // 1.20-K1(D-48 P2):滚动断点求值身份(每请求经 rollingCacheAllowed 对 env 求值,非构造期定值)
773
+ const rollingIdentity = opts.rollingIdentity ?? id;
690
774
  const baseUrl = opts.baseUrl.replace(/\/+$/, '');
691
775
  assertTlsOrLoopback(baseUrl);
692
776
  const url = `${baseUrl}/v1/messages`;
@@ -714,7 +798,10 @@ export function createAnthropicAdapter(opts) {
714
798
  const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts);
715
799
  apiKey = opts.credentials.reveal(ref, {});
716
800
  }
717
- return toAnthropic(req, apiKey);
801
+ return toAnthropic(req, apiKey, {
802
+ rolling: rollingCacheAllowed(rollingIdentity),
803
+ rollingBreakpoints: rollingBreakpoints(),
804
+ });
718
805
  },
719
806
  fetchImpl,
720
807
  signal: req.signal,
@@ -86,6 +86,12 @@ export interface LlmChunk {
86
86
  };
87
87
  /** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
88
88
  usage?: LlmUsage;
89
+ /** thinking 块文本增量(content_block_delta.thinking_delta) */
90
+ thinkingDelta?: string;
91
+ /** thinking 块签名增量(signature_delta——真 Anthropic thinking 块回传须携签名) */
92
+ thinkingSignatureDelta?: string;
93
+ /** redacted_thinking 块 data 全文(content_block_start 单发;回传形态 {type:'redacted_thinking',data}) */
94
+ thinkingRedacted?: string;
89
95
  }
90
96
  /** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
91
97
  export interface LlmAdapter {
package/dist/sbom.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bomFormat": "CAR-SBOM",
3
3
  "specVersion": "0.1",
4
- "version": "1.19.0",
4
+ "version": "1.21.0",
5
5
  "components": [
6
6
  {
7
7
  "type": "file",
@@ -11,12 +11,12 @@
11
11
  {
12
12
  "type": "file",
13
13
  "path": "src/cli.ts",
14
- "sha256": "833d9d33e281115d1ae50858028ce82b930a530b9ef513232f02bf3b94269fcb"
14
+ "sha256": "914df86dd59907d6aa24b3d339ee1e4c6bf366a63ac553199ad172760aa29539"
15
15
  },
16
16
  {
17
17
  "type": "file",
18
18
  "path": "src/dx/doctor.ts",
19
- "sha256": "bf0e39f62edbafe12f91100c009879cbc73ff1cd7628a7851b264dbef1fd5a4c"
19
+ "sha256": "32ea488aea0dcff1709f0244eff5d417b964e6f2eef5abab5a21da883da2e059"
20
20
  },
21
21
  {
22
22
  "type": "file",
@@ -56,7 +56,7 @@
56
56
  {
57
57
  "type": "file",
58
58
  "path": "src/load/config.ts",
59
- "sha256": "529e2c5d000e9cdb8835a554263c4f8881a1eed594176571c5eca53dddd17f47"
59
+ "sha256": "39f0185b46ed580ee3b9cac6a028fe6a3eddbf6257087bc0406b11f3e1b94d86"
60
60
  },
61
61
  {
62
62
  "type": "file",
@@ -66,7 +66,7 @@
66
66
  {
67
67
  "type": "file",
68
68
  "path": "src/load/pool.ts",
69
- "sha256": "e52909bcad4ba2270e62cb1f509abb6fd6e2e017e2f1b0ce8f326ade8d876ee3"
69
+ "sha256": "08595f3786eec9318644f2b183dad6f5d6db3cf90975a92bd4887a1d6591de62"
70
70
  },
71
71
  {
72
72
  "type": "file",
@@ -161,7 +161,7 @@
161
161
  {
162
162
  "type": "file",
163
163
  "path": "src/runtime-core/chatStep.ts",
164
- "sha256": "da87f5e0231a9d0dc8455bac6057ce7f5d45eeffa7534b9ae44be7c0a42e64f4"
164
+ "sha256": "b7cf35ba06ebaae339a89da0e4df08b5ae310a60a97b5201de54f092fc8758ab"
165
165
  },
166
166
  {
167
167
  "type": "file",
@@ -176,7 +176,7 @@
176
176
  {
177
177
  "type": "file",
178
178
  "path": "src/runtime-core/llm.ts",
179
- "sha256": "7aadf5f673e4db34c0ae45e44c48e9b9c7f36a970a0dd4d36285dc5ac74fa32f"
179
+ "sha256": "71bf8926b162fc8ae05343953405a225b5630ba85196dc71727524de18196cc3"
180
180
  },
181
181
  {
182
182
  "type": "file",
@@ -201,7 +201,7 @@
201
201
  {
202
202
  "type": "file",
203
203
  "path": "src/runtime-core/types.ts",
204
- "sha256": "15229f894f8432ce29e8c1b3dcefded54a1e1220e0c6da93c5f24a68213147df"
204
+ "sha256": "d9ff638ea4de9b89d56238f2d7fd65c07dd29323fcb9906e72b6877618a360a6"
205
205
  },
206
206
  {
207
207
  "type": "file",
@@ -251,7 +251,7 @@
251
251
  {
252
252
  "type": "file",
253
253
  "path": "src/session/log.ts",
254
- "sha256": "435fc7336e5de1b96688d3c973aa1129ae39e7347a324d5f15aaabe77a6e1a71"
254
+ "sha256": "9b3a081bfa60e046166cce11203848402f52d4e9b9d0f94ad2e9dc1f0ebadd9c"
255
255
  },
256
256
  {
257
257
  "type": "file",
@@ -271,7 +271,7 @@
271
271
  {
272
272
  "type": "file",
273
273
  "path": "package.json",
274
- "sha256": "7f407eeeabf8565fbe5340ef8c3041ba167b082937b2544a751247a36b7ec7a5"
274
+ "sha256": "a6ae52801faedf9b64c49d0be8c5a7c22204697bf22d78c7253494bf28677b9d"
275
275
  },
276
276
  {
277
277
  "type": "file",
@@ -1,4 +1,4 @@
1
- export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory';
1
+ export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory' | 'thinking';
2
2
  export type Actor = 'user' | 'model' | 'plugin' | 'runtime';
3
3
  export interface SessionEvent {
4
4
  seq: number;
@@ -122,15 +122,45 @@ export class SessionLog {
122
122
  const out = [];
123
123
  if (compactEvent)
124
124
  out.push({ role: 'system', content: (compactEvent.payload.summary) });
125
+ // 1.21(R-TOKENHUB-1 / D-T3):thinking 投影——同 turn 内 thinking 事件附加到首个后续
126
+ // toolCall 消息(原始 provider assistant 消息 = [thinking, ...tool_use] 的重放形态);
127
+ // standalone(无后续 toolCall)/ 被 user·system 隔断 / 跨 turn 不投影(实证:普通两轮
128
+ // 历史不要求回传);assistant(text) 同 turn 不隔断(链序 thinking→assistant→toolCall);
129
+ // 无 thinking 事件投影逐字节不变(D-T5 兼容线,N1 快照自洽)。
130
+ let pendingThinking = null;
125
131
  for (const e of projected) {
132
+ if (pendingThinking && (e.turnId !== pendingThinking.turnId || e.kind === 'user' || e.kind === 'system'))
133
+ pendingThinking = null;
134
+ if (e.kind === 'thinking') {
135
+ const p = typeof e.payload === 'string' ? e.payload : '';
136
+ if (p.length || e.meta?.redacted === true) {
137
+ pendingThinking = {
138
+ turnId: e.turnId,
139
+ text: p,
140
+ ...(typeof e.meta?.signature === 'string' && e.meta.signature ? { signature: e.meta.signature } : {}),
141
+ ...(e.meta?.redacted === true ? { redacted: true } : {}),
142
+ };
143
+ }
144
+ continue; // thinking 不单独成消息(无独立回传语义)
145
+ }
126
146
  if (e.kind === 'system')
127
147
  out.push({ role: 'system', content: e.payload });
128
148
  else if (e.kind === 'user')
129
149
  out.push({ role: 'user', content: e.payload });
130
150
  else if (e.kind === 'assistant')
131
151
  out.push({ role: 'assistant', content: e.payload });
132
- else if (e.kind === 'toolCall')
133
- out.push({ role: 'assistant', toolCall: e.payload });
152
+ else if (e.kind === 'toolCall') {
153
+ const msg = { role: 'assistant', toolCall: e.payload };
154
+ if (pendingThinking) {
155
+ msg.thinking = {
156
+ text: pendingThinking.text,
157
+ ...(pendingThinking.signature ? { signature: pendingThinking.signature } : {}),
158
+ ...(pendingThinking.redacted ? { redacted: true } : {}),
159
+ };
160
+ pendingThinking = null; // 首 toolCall 消费即清——同 turn 多 toolCall = 同一原始消息,单块重放
161
+ }
162
+ out.push(msg);
163
+ }
134
164
  else if (e.kind === 'toolResult') {
135
165
  const id = e.payload?.id;
136
166
  if (typeof id === 'string' && lastSeqById.get(id) !== e.seq)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lqc123qwe/car-runtime",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Composable Agent Runtime (CAR) - microkernel plugin system with sandboxed PTC, append-only hashed session log, and MCP host bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -211,6 +211,16 @@ async function main(): Promise<number> {
211
211
  // 1.17-K4(D-48 P1 / V1-A):提示缓存断点关断 flag——flag > env(置 env 后适配器内部决策;
212
212
  // 池/单端点两分支通用,须先于适配器创建)
213
213
  if (rest.includes('--no-prompt-cache')) process.env.CAR_LLM_PROMPT_CACHE = 'off'
214
+ // 1.20-K1(D-48 P2):滚动断点白名单——flag > env > config(env = 适配器每请求求值的
215
+ // 运行时事实源;'off' = 显式清空;池成员身份在 pool 装配处按 m.id 传入,不走本 env 的单端点语义)
216
+ const rollingFlag = flagValue(rest, '--prompt-cache-rolling')
217
+ if (rollingFlag !== undefined) process.env.CAR_LLM_PROMPT_CACHE_ROLLING = rollingFlag
218
+ else if (!process.env.CAR_LLM_PROMPT_CACHE_ROLLING && llm.promptCache?.rolling?.adapters?.length) {
219
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING = llm.promptCache.rolling.adapters.join(',')
220
+ }
221
+ if (llm.promptCache?.rolling?.breakpoints !== undefined) {
222
+ process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS = String(llm.promptCache.rolling.breakpoints)
223
+ }
214
224
  const credService = new CredentialService()
215
225
  const core = new RuntimeCore(credService)
216
226
  core.bindContext(ctx)
package/src/dx/doctor.ts CHANGED
@@ -123,12 +123,39 @@ export function doctorModelReadiness(opts: { env?: NodeJS.ProcessEnv; cwd?: stri
123
123
  } catch { /* 全落空 = not-found(登记口径,非错误) */ }
124
124
  }
125
125
  const configured = !!baseUrl && !!model
126
+ // 1.20-K1(D-48 P2):提示缓存策略可视(断点 = 传输层注解;rolling = 白名单缺省关闭,
127
+ // 登记前置 = 该端点 rolling 形态探针 VERDICT=A——1.20 设计 §3.5 流程门控)
128
+ const cacheOff = env.CAR_LLM_PROMPT_CACHE === 'off'
129
+ const rollingEnv = env.CAR_LLM_PROMPT_CACHE_ROLLING
130
+ const cfgRolling = cfg.error ? undefined : cfg.config.llm?.promptCache?.rolling
131
+ const rollingList = rollingEnv !== undefined
132
+ ? rollingEnv.split(',').map(s => s.trim()).filter(Boolean)
133
+ : (cfgRolling?.adapters ?? [])
134
+ const rollingShown = rollingEnv !== undefined
135
+ ? rollingList
136
+ : rollingList.length ? rollingList : undefined
137
+ // 白名单未匹配告警(fail-visible 不阻断——成员可能已删,但登记错误必须可见)
138
+ const knownIdentities = new Set<string>()
139
+ if (!cfg.error && cfg.config.modelPool?.endpoints.length) {
140
+ cfg.config.modelPool.endpoints.forEach((ep, i) => knownIdentities.add(ep.id ?? `ep-${i + 1}`))
141
+ } else if (adapterId === 'anthropic') {
142
+ knownIdentities.add('anthropic')
143
+ }
144
+ const unmatched = rollingList.filter(id => !knownIdentities.has(id))
145
+ const cacheParts = [
146
+ `promptCache=${cacheOff ? 'off' : 'on'}`,
147
+ rollingShown === undefined ? 'rolling=缺省关闭(白名单须探针 VERDICT=A 后登记——llm.promptCache.rolling.adapters / CAR_LLM_PROMPT_CACHE_ROLLING)'
148
+ : rollingShown.includes('off') ? 'rolling=off(显式清空)'
149
+ : `rolling=[${rollingShown.join(',')}]${cfgRolling?.breakpoints !== undefined ? ` breakpoints=${cfgRolling.breakpoints}` : ''}`,
150
+ unmatched.length ? `⚠️ 白名单含未匹配身份:${unmatched.join(',')}(无在册单端点 anthropic / 池成员 id 对应——登记错误或成员已删)` : '',
151
+ ].filter(Boolean)
126
152
  const parts = [
127
153
  configured ? `baseUrl=${baseUrl} model=${model}${adapterId ? ` adapter=${adapterId}` : ''}` : 'baseUrl/model 未配置(car run 真路径需 llm.* 配置或 CAR_LLM_BASE_URL/CAR_LLM_MODEL——见 car doctor / 配置指南)',
128
154
  credential === 'keychain' ? `凭据=keychain 命中(car-runtime/${provider},值不打印)`
129
155
  : credential === 'env' ? '凭据=env 命中(显式开启 fallback,值不打印)'
130
156
  : '凭据未就绪(keychain 未命中且 env fallback 未开或变量缺席——A080001 会引导至此)',
131
157
  `envFallback=${envFallback}`,
158
+ ...cacheParts,
132
159
  ]
133
160
  return { configured, baseUrl: baseUrl ?? '', model: model ?? '', credential, envFallback, detail: parts.join(';') }
134
161
  }
@@ -56,6 +56,18 @@ export interface LlmConfig {
56
56
  compaction?: CompactionConfig
57
57
  /** 1.16-K3(D-K2-2):子 Agent 配置(缺省 = 继承父会话模型锚点 Q-A2 + 宿主护栏常量 Q-A3) */
58
58
  subagent?: SubagentConfig
59
+ /** 1.20-K1(D-48 P2):提示缓存滚动断点白名单(缺省关闭;运行时事实源 = CAR_LLM_PROMPT_CACHE_ROLLING,cli 按 flag > env > config 落 env) */
60
+ promptCache?: PromptCacheConfig
61
+ }
62
+
63
+ /** 1.20-K1(D-48 P2):滚动断点白名单(1.20 设计 §3.4/§3.5——逐条确证,无通配) */
64
+ export interface PromptCacheConfig {
65
+ rolling?: {
66
+ /** 路由身份白名单(单端点 'anthropic' / 池成员 id);空表显式拒绝(禁静默 no-op);登记前置 = 该端点探针 VERDICT=A */
67
+ adapters?: string[]
68
+ /** 滚动断点数(1..3 钳制;缺省 1——多挂对同一前缀重复写 1.25x 缓存,1.20 设计 §3.3) */
69
+ breakpoints?: number
70
+ }
59
71
  }
60
72
 
61
73
  /** 1.16-K3(D-K2-2):子 Agent 配置面——宿主编排消费(内核自身不装配编排;schema 只登记键位) */
@@ -264,6 +276,17 @@ const rawSchema = z.object({
264
276
  }).strict()
265
277
  .refine(v => Object.keys(v).length > 0, { message: '至少登记一个键(model/maxConcurrent/maxCumulative;无子 Agent 配置需求请移除 llm.subagent 键,行为与 1.15.2 一致)' })
266
278
  .optional(),
279
+ // 1.20-K1(D-48 P2):滚动断点白名单(路由身份精确匹配;adapters 空表显式拒——禁静默 no-op)
280
+ promptCache: z.object({
281
+ rolling: z.object({
282
+ adapters: z.array(nonEmptyTrimmed(), { invalid_type_error: '必须为字符串数组' })
283
+ .min(1, { message: '至少登记一个路由身份(空表 = 静默 no-op 显式拒绝;不需要滚动断点请移除 llm.promptCache 键)' })
284
+ .optional(),
285
+ breakpoints: positiveInt()
286
+ .refine(v => v <= 3, { message: '必须为 1..3(provider 断点硬限 4,system 占 1)' })
287
+ .optional(),
288
+ }).strict().optional(),
289
+ }).strict().optional(),
267
290
  }).strict().optional(),
268
291
  // 1.14-S4(K-POOL-1 / D-42):模型池登记(多端点 fallback + health + routing/decide 落链)
269
292
  modelPool: modelPoolSchema.optional(),
@@ -324,6 +347,16 @@ function parseCarConfig(raw: unknown): { config: CarConfig; error?: string } {
324
347
  if (d.llm.subagent.maxCumulative !== undefined) sg.maxCumulative = d.llm.subagent.maxCumulative
325
348
  llm.subagent = sg
326
349
  }
350
+ if (d.llm.promptCache) {
351
+ const pc: PromptCacheConfig = {}
352
+ if (d.llm.promptCache.rolling) {
353
+ const r: NonNullable<PromptCacheConfig['rolling']> = {}
354
+ if (d.llm.promptCache.rolling.adapters !== undefined) r.adapters = [...d.llm.promptCache.rolling.adapters]
355
+ if (d.llm.promptCache.rolling.breakpoints !== undefined) r.breakpoints = d.llm.promptCache.rolling.breakpoints
356
+ pc.rolling = r
357
+ }
358
+ llm.promptCache = pc
359
+ }
327
360
  out.llm = llm
328
361
  }
329
362
  if (d.mcp) {
@@ -469,6 +502,8 @@ export function mergeLlmConfig(env: NodeJS.ProcessEnv, cfg: CarConfig, flags: {
469
502
  ...(adapterId ? { adapterId } : {}),
470
503
  ...(maxTokens !== undefined ? { maxTokens } : {}),
471
504
  allowEnvFallback,
505
+ // 1.20-K1(D-48 P2):滚动断点声明层透传(cli 层按 flag > env > config 落运行时 env)
506
+ ...(cfg.llm?.promptCache ? { promptCache: cfg.llm.promptCache } : {}),
472
507
  }
473
508
  }
474
509
 
package/src/load/pool.ts CHANGED
@@ -304,6 +304,9 @@ export function assembleModelPool(opts: ModelPoolAssemblyOptions): {
304
304
  ? createAnthropicAdapter({
305
305
  baseUrl: m.baseUrl,
306
306
  credentials: opts.credentials,
307
+ // 1.20-K1(D-48 P2):滚动断点按成员路由身份求值——池成员适配器 id 恒 'anthropic'
308
+ //(装配未透传),禁按其求值(一端点解锁 = 全池解锁,1.20 设计 §3.1)
309
+ rollingIdentity: m.id,
307
310
  ...(m.keyEnv !== undefined ? { credentialEnvVars: [m.keyEnv] } : {}),
308
311
  ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
309
312
  ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
@@ -81,7 +81,14 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
81
81
  const messages = log.deriveMessages(undefined, { activeTurnId: turnId }).map(m => ({
82
82
  role: m.role as 'system' | 'user' | 'assistant' | 'toolResult',
83
83
  content: (m as unknown as { toolCall?: unknown }).toolCall !== undefined
84
- ? { toolCall: (m as unknown as { toolCall: unknown }).toolCall }
84
+ ? {
85
+ toolCall: (m as unknown as { toolCall: unknown }).toolCall,
86
+ // 1.21(R-TOKENHUB-1 / D-T3/D-T4):投影层附加的同 turn thinking 三元组随 toolCall
87
+ // 载荷透传(适配器回传消费——toAnthropic 前置 thinking 块;openai 形忽略该键)
88
+ ...((m as unknown as { thinking?: unknown }).thinking !== undefined
89
+ ? { thinking: (m as unknown as { thinking: unknown }).thinking }
90
+ : {}),
91
+ }
85
92
  : m.content,
86
93
  }))
87
94
 
@@ -89,6 +96,11 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
89
96
  const redactor = new StreamRedactor()
90
97
  let text = ''
91
98
  const calls = new Map<number, { id?: string; name?: string; argsBuf: string }>()
99
+ // 1.21(R-TOKENHUB-1 / D-T1/D-T2):thinking 捕获累积——禁改写(回传签名完整性 > 脱敏
100
+ // 改写),provider 事实字节保真;redacted_thinking = data 全文单发覆盖
101
+ let thinkingText = ''
102
+ let thinkingSignature = ''
103
+ let thinkingRedacted: string | undefined
92
104
  let stopReason: ModelStep['stopReason'] | undefined
93
105
  let errorDetail: string | undefined
94
106
  let usage: LlmUsage | undefined
@@ -124,6 +136,10 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
124
136
  if (chunk.toolCallDelta.argumentsDelta) cur.argsBuf += chunk.toolCallDelta.argumentsDelta
125
137
  calls.set(idx, cur)
126
138
  }
139
+ // 1.21(R-TOKENHUB-1 / D-T1):thinking 三面捕获(text/signature 增量累积、redacted 单发覆盖)
140
+ if (chunk.thinkingDelta !== undefined) thinkingText += chunk.thinkingDelta
141
+ if (chunk.thinkingSignatureDelta !== undefined) thinkingSignature += chunk.thinkingSignatureDelta
142
+ if (chunk.thinkingRedacted !== undefined) thinkingRedacted = chunk.thinkingRedacted
127
143
  if (chunk.error) errorDetail = chunk.error.message
128
144
  if (chunk.usage) usage = chunk.usage // D-33:附着于最后一个 chunk——后到覆盖
129
145
  if (chunk.finishReason !== undefined) stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason
@@ -157,6 +173,21 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
157
173
  })
158
174
  }
159
175
 
176
+ // 5a. 1.21(R-TOKENHUB-1 / D-T2):thinking 事件落链(actor='model',先于 assistant/toolCall
177
+ // ——投影层按同 turn 首 toolCall 附加回传)。payload = thinking 原文禁改写(签名完整性 >
178
+ // 脱敏改写——改写即回传校验失败);secretsRedacted 仅计数留痕(审计信号,不复写);
179
+ // redacted 形态 payload = data 全文(meta.redacted=true)。无 thinking chunk = 零新事件
180
+ // (D-T5 字节兼容,链形态与 1.20 全等)。
181
+ if (thinkingText.length || thinkingRedacted !== undefined) {
182
+ const thinkingPayload = thinkingRedacted ?? thinkingText
183
+ const secretsInThinking = scanSecrets(thinkingPayload).hits.length
184
+ log.append('model', 'thinking', turnId, thinkingPayload, {
185
+ ...(thinkingRedacted !== undefined ? { redacted: true } : {}),
186
+ ...(thinkingSignature ? { signature: thinkingSignature } : {}),
187
+ ...(secretsInThinking ? { secretsRedacted: secretsInThinking } : {}),
188
+ })
189
+ }
190
+
160
191
  // 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
161
192
  // flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
162
193
  let secretsRedacted = 0
@@ -520,6 +520,12 @@ export interface AnthropicAdapterOptions {
520
520
  defaultMaxTokens?: number
521
521
  /** 1.14-S4(K-POOL-1):成员级凭据 env 变量名引用(modelPool keyEnv——resolve 精确候选;keychain 链保持在前) */
522
522
  credentialEnvVars?: string[]
523
+ /**
524
+ * 1.20-K1(D-48 P2):滚动断点求值身份。缺省 = id(单端点 'anthropic');
525
+ * **池成员必须显式传 m.id**——池成员适配器 id 恒 'anthropic'(装配未透传成员标识),
526
+ * 按其求值 = 白名单一端点解锁全池(1.20 设计 §3.1 专项锁,S66/AC-9)。
527
+ */
528
+ rollingIdentity?: string
523
529
  }
524
530
 
525
531
  /** stop_reason → CAR finishReason(未映射 fail-visible——openai FINISH_MAP 同纪律) */
@@ -579,12 +585,68 @@ function mergeAnthropicUsage(
579
585
  * 投影/快照比对,N1 零影响;生效有 provider 侧最小长度门槛(~1024 tokens)——短 system
580
586
  * 静默不生效应以 usage.cacheCreationTokens 观测,禁以「配了就该生效」推断。关断 =
581
587
  * CAR_LLM_PROMPT_CACHE=off(cli flag --no-prompt-cache 置本 env;A/B 与故障回退通道)。
588
+ * 1.20-K1(D-48 P2):滚动断点——白名单命中时在尾部消息挂第二处断点(缺省 1 个,只块化命中
589
+ * 消息),键 = 路由身份(rollingIdentity,池成员 = m.id);CAR_LLM_PROMPT_CACHE_ROLLING
590
+ * 逗号清单('off' = 显式清空,无通配)+ CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS(1..3 钳制)。
582
591
  */
583
592
  function promptCacheOff(): boolean {
584
593
  return process.env.CAR_LLM_PROMPT_CACHE === 'off'
585
594
  }
586
595
 
587
- function toAnthropic(req: LlmRequest, apiKey: string) {
596
+ /**
597
+ * 1.20-K1(D-48 P2):滚动断点白名单求值(每请求求值,与 promptCacheOff 同址同时序)。
598
+ * 键 = 路由身份:单端点 = adapter id('anthropic')/ 池成员 = 显式 rollingIdentity(m.id)——
599
+ * **禁按 adapter.id 给池成员求值**(池成员适配器 id 恒 'anthropic'——pool 装配未透传成员标识,
600
+ * 一端点解锁 = 全池解锁,1.20 设计 §3.1)。off 连坐最高优先;清单值 'off' = 显式清空;
601
+ * 无 '*' 通配(通配废掉逐条确证);大小写敏感(归一化会掩盖登记错误)。
602
+ */
603
+ export function rollingCacheAllowed(identity: string): boolean {
604
+ if (promptCacheOff()) return false
605
+ const raw = process.env.CAR_LLM_PROMPT_CACHE_ROLLING
606
+ if (!raw) return false
607
+ const list = raw.split(',').map(s => s.trim()).filter(Boolean)
608
+ if (!list.length || list.includes('off')) return false
609
+ return list.includes(identity)
610
+ }
611
+
612
+ // 1.20-K1:滚动断点数——provider 硬限 4,system 断点(P1)恒占 1;缺省 1(多挂 = 对同一前缀
613
+ // 重复写 1.25x 缓存,1.20 设计 §3.3)。env 非法/缺席回落缺省,超限钳制(AC-8)
614
+ const ROLLING_BREAKPOINTS_DEFAULT = 1
615
+ const ROLLING_BREAKPOINTS_MAX = 3
616
+
617
+ function rollingBreakpoints(): number {
618
+ const raw = Number(process.env.CAR_LLM_PROMPT_CACHE_ROLLING_BREAKPOINTS)
619
+ if (!Number.isFinite(raw) || raw < 1) return ROLLING_BREAKPOINTS_DEFAULT
620
+ return Math.min(Math.trunc(raw), ROLLING_BREAKPOINTS_MAX)
621
+ }
622
+
623
+ /**
624
+ * 1.20-K1(D-48 P2):滚动断点放置——从尾部往前命中即挂,只块化被挂消息,其余消息维持
625
+ * 字符串(1.20 设计 §3.2:全量块化零收益高风险)。断点挂**末块**(tool_result / tool_use
626
+ * 多块形态不拆不重组,语义不变);空串 / 空数组 / 非对象末块无可挂载体 → 跳过前移(不换位不补造)。
627
+ * 纯传输层注解:只改本请求出站体,不进 SessionLog / 投影(N1 零影响,S59 同红线)。
628
+ */
629
+ function attachRollingBreakpoints(turns: Array<{ role: 'user' | 'assistant'; content: unknown }>, count: number): void {
630
+ let attached = 0
631
+ for (let i = turns.length - 1; i >= 0 && attached < count; i--) {
632
+ const t = turns[i]!
633
+ if (typeof t.content === 'string') {
634
+ if (!t.content.length) continue
635
+ t.content = [{ type: 'text', text: t.content, cache_control: { type: 'ephemeral' } }]
636
+ attached++
637
+ continue
638
+ }
639
+ if (Array.isArray(t.content) && t.content.length) {
640
+ const last = t.content[t.content.length - 1] as Record<string, unknown> | undefined
641
+ if (last && typeof last === 'object' && !Array.isArray(last)) {
642
+ last.cache_control = { type: 'ephemeral' }
643
+ attached++
644
+ }
645
+ }
646
+ }
647
+ }
648
+
649
+ function toAnthropic(req: LlmRequest, apiKey: string, cache: { rolling: boolean; rollingBreakpoints: number }) {
588
650
  const system: string[] = []
589
651
  const turns: Array<{ role: 'user' | 'assistant'; content: unknown }> = []
590
652
  let pendingToolResults: Array<Record<string, unknown>> = []
@@ -611,12 +673,25 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
611
673
  flushToolResults()
612
674
  if (m.role === 'assistant' && m.content != null && typeof m.content === 'object' && 'toolCall' in (m.content as object)) {
613
675
  const tc = (m.content as { toolCall: { id: string; tool: string; args: unknown } }).toolCall
614
- turns.push({ role: 'assistant', content: [{ type: 'tool_use', id: tc.id, name: tc.tool, input: tc.args ?? {} }] })
676
+ // 1.21(R-TOKENHUB-1 / D-T4):工具环回传——投影层附加的 thinking(同 turn 原始 assistant
677
+ // 消息的 thinking 块)前置重放(Anthropic 规范:thinking 须位于同消息 text/tool_use 之前;
678
+ // tokenhub 等网关对缺回传 400——R-TOKENHUB-1 实证)。无 thinking = 形态与 1.20 逐字节一致
679
+ // (D-T5 兼容线,golden 不动)。
680
+ const th = (m.content as { thinking?: { text?: string; signature?: string; redacted?: boolean } }).thinking
681
+ const thinkingBlock = th?.text?.length
682
+ ? th.redacted
683
+ ? { type: 'redacted_thinking', data: th.text }
684
+ : { type: 'thinking', thinking: th.text, ...(th.signature ? { signature: th.signature } : {}) }
685
+ : undefined
686
+ turns.push({ role: 'assistant', content: [...(thinkingBlock ? [thinkingBlock] : []), { type: 'tool_use', id: tc.id, name: tc.tool, input: tc.args ?? {} }] })
615
687
  } else {
616
688
  turns.push({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })
617
689
  }
618
690
  }
619
691
  flushToolResults()
692
+ // 1.20-K1(D-48 P2):滚动断点在白名单命中时挂尾部消息(缺省 1 个)——system 断点(P1)
693
+ // 与消息断点独立,off 时两者俱灭(rollingCacheAllowed 内已连坐)
694
+ if (cache.rolling) attachRollingBreakpoints(turns, cache.rollingBreakpoints)
620
695
  // 1.14-S1(K-MP-1 / D-39):思考参数——effort → thinking.budget_tokens(ANTHROPIC_THINKING_BUDGET 表);
621
696
  // off/缺席不下发;budget ≥ max_tokens 违反 Messages API 约束(max_tokens 必须大于 budget_tokens)→
622
697
  // 显式报错提示调高(fail-visible,禁静默改写 max_tokens)
@@ -681,6 +756,9 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model:
681
756
  const block = evt.content_block
682
757
  if (block?.type === 'tool_use') {
683
758
  yield { toolCallDelta: { index: evt.index, id: block.id, name: block.name } }
759
+ } else if (block?.type === 'redacted_thinking' && typeof block.data === 'string' && block.data.length) {
760
+ // 1.21(R-TOKENHUB-1 / D-T1):redacted_thinking 全文单发(回传形态 {type:'redacted_thinking',data})
761
+ yield { thinkingRedacted: block.data }
684
762
  }
685
763
  } else if (type === 'content_block_delta') {
686
764
  const d = evt.delta
@@ -688,8 +766,13 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model:
688
766
  yield { delta: d.text }
689
767
  } else if (d?.type === 'input_json_delta' && typeof d.partial_json === 'string' && d.partial_json.length) {
690
768
  yield { toolCallDelta: { index: evt.index, argumentsDelta: d.partial_json } }
769
+ } else if (d?.type === 'thinking_delta' && typeof d.thinking === 'string' && d.thinking.length) {
770
+ // 1.21(R-TOKENHUB-1 / D-T1):thinking 文本增量捕获(原容错跳过 → 落链 + 工具环回传)
771
+ yield { thinkingDelta: d.thinking }
772
+ } else if (d?.type === 'signature_delta' && typeof d.signature === 'string' && d.signature.length) {
773
+ yield { thinkingSignatureDelta: d.signature }
691
774
  }
692
- // thinking_delta / citations_delta 等非 CAR 消费面:容错跳过
775
+ // citations_delta 等其余非 CAR 消费面:容错跳过
693
776
  } else if (type === 'message_start') {
694
777
  // 1.11-S1(D-33):input 侧采集(usage 嵌套在 message 下)
695
778
  const u = (evt.message as Record<string, unknown> | undefined)?.usage
@@ -718,6 +801,8 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model:
718
801
 
719
802
  export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapter & { id: string } {
720
803
  const id = opts.id ?? 'anthropic'
804
+ // 1.20-K1(D-48 P2):滚动断点求值身份(每请求经 rollingCacheAllowed 对 env 求值,非构造期定值)
805
+ const rollingIdentity = opts.rollingIdentity ?? id
721
806
  const baseUrl = opts.baseUrl.replace(/\/+$/, '')
722
807
  assertTlsOrLoopback(baseUrl)
723
808
  const url = `${baseUrl}/v1/messages`
@@ -746,7 +831,10 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
746
831
  const ref = opts.credentials.resolve(opts.provider ?? id, credResolveOpts)
747
832
  apiKey = opts.credentials.reveal(ref, {})
748
833
  }
749
- return toAnthropic(req, apiKey)
834
+ return toAnthropic(req, apiKey, {
835
+ rolling: rollingCacheAllowed(rollingIdentity),
836
+ rollingBreakpoints: rollingBreakpoints(),
837
+ })
750
838
  },
751
839
  fetchImpl,
752
840
  signal: req.signal,
@@ -76,6 +76,14 @@ export interface LlmChunk {
76
76
  error?: { code: string; message: string }
77
77
  /** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
78
78
  usage?: LlmUsage
79
+ // —— 1.21(R-TOKENHUB-1 / D-T1)thinking 捕获加法——anthropic SSE thinking 面捕获;
80
+ // 禁改写(回传签名完整性 > 脱敏改写);缺失 = provider 未发 thinking,禁补造
81
+ /** thinking 块文本增量(content_block_delta.thinking_delta) */
82
+ thinkingDelta?: string
83
+ /** thinking 块签名增量(signature_delta——真 Anthropic thinking 块回传须携签名) */
84
+ thinkingSignatureDelta?: string
85
+ /** redacted_thinking 块 data 全文(content_block_start 单发;回传形态 {type:'redacted_thinking',data}) */
86
+ thinkingRedacted?: string
79
87
  }
80
88
 
81
89
  /** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto'
11
11
  import { readFileSync } from 'node:fs'
12
12
  import { decodeLogBuffer, splitJsonlLines } from './format.ts'
13
13
 
14
- export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变;1.14-S2 加法扩展(K-MP-2 / D-40):历史压缩事件(actor='runtime',append-only 只追加——payload={summary, compactedUpTo, ...},投影按 compactedUpTo 前缀折叠为摘要,原事件链不改写);1.14-S4 加法扩展(K-POOL-1 / D-42):模型池路由决策(actor='runtime',payload.type='decide',selected/reason/retries/attempts——禁静默换模型;model_visible=0 不进模型消息流,deriveMessages 不投影——同 usage 纪律);1.15-S1 加法扩展(K-MEM-1 / D-46):白盒记忆操作事件(actor='user' 宿主代理用户操作,turnId='memory' 会话级标识同 'compaction' 先例,payload.type='write'|'edit'|'delete'|'rollback'——审计链同源差异化面,链即版本库〔rollback 恢复数据源=链上 payload.before/after 全文〕;model_visible=0 不进模型消息流——记忆注入走宿主装配层 system ctx 行,deriveMessages 不投影);
14
+ export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' | 'compact' | 'routing' | 'memory' | 'thinking' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变;1.14-S2 加法扩展(K-MP-2 / D-40):历史压缩事件(actor='runtime',append-only 只追加——payload={summary, compactedUpTo, ...},投影按 compactedUpTo 前缀折叠为摘要,原事件链不改写);1.14-S4 加法扩展(K-POOL-1 / D-42):模型池路由决策(actor='runtime',payload.type='decide',selected/reason/retries/attempts——禁静默换模型;model_visible=0 不进模型消息流,deriveMessages 不投影——同 usage 纪律);1.15-S1 加法扩展(K-MEM-1 / D-46):白盒记忆操作事件(actor='user' 宿主代理用户操作,turnId='memory' 会话级标识同 'compaction' 先例,payload.type='write'|'edit'|'delete'|'rollback'——审计链同源差异化面,链即版本库〔rollback 恢复数据源=链上 payload.before/after 全文〕;model_visible=0 不进模型消息流——记忆注入走宿主装配层 system ctx 行,deriveMessages 不投影);1.21 加法扩展(R-TOKENHUB-1 / D-T2):thinking 事件(actor='model',payload=thinking 原文禁改写,meta={signature?, redacted?, secretsRedacted?}——先于 assistant/toolCall 落链;投影 = 同 turn 首个 toolCall 消息附加回传(工具环 reasoning_content 契约,Anthropic thinking+tool_use 规范同款);standalone/隔断/跨 turn 不投影;无 thinking 事件链投影逐字节不变);
15
15
  export type Actor = 'user' | 'model' | 'plugin' | 'runtime'
16
16
  export interface SessionEvent {
17
17
  seq: number
@@ -146,11 +146,41 @@ export class SessionLog {
146
146
  }
147
147
  const out: Array<{ role: string; [k: string]: unknown }> = []
148
148
  if (compactEvent) out.push({ role: 'system', content: ((compactEvent.payload as { summary: string }).summary) })
149
+ // 1.21(R-TOKENHUB-1 / D-T3):thinking 投影——同 turn 内 thinking 事件附加到首个后续
150
+ // toolCall 消息(原始 provider assistant 消息 = [thinking, ...tool_use] 的重放形态);
151
+ // standalone(无后续 toolCall)/ 被 user·system 隔断 / 跨 turn 不投影(实证:普通两轮
152
+ // 历史不要求回传);assistant(text) 同 turn 不隔断(链序 thinking→assistant→toolCall);
153
+ // 无 thinking 事件投影逐字节不变(D-T5 兼容线,N1 快照自洽)。
154
+ let pendingThinking: { turnId: string; text: string; signature?: string; redacted?: boolean } | null = null
149
155
  for (const e of projected) {
156
+ if (pendingThinking && (e.turnId !== pendingThinking.turnId || e.kind === 'user' || e.kind === 'system')) pendingThinking = null
157
+ if (e.kind === 'thinking') {
158
+ const p = typeof e.payload === 'string' ? e.payload : ''
159
+ if (p.length || e.meta?.redacted === true) {
160
+ pendingThinking = {
161
+ turnId: e.turnId,
162
+ text: p,
163
+ ...(typeof e.meta?.signature === 'string' && e.meta.signature ? { signature: e.meta.signature } : {}),
164
+ ...(e.meta?.redacted === true ? { redacted: true } : {}),
165
+ }
166
+ }
167
+ continue // thinking 不单独成消息(无独立回传语义)
168
+ }
150
169
  if (e.kind === 'system') out.push({ role: 'system', content: e.payload })
151
170
  else if (e.kind === 'user') out.push({ role: 'user', content: e.payload })
152
171
  else if (e.kind === 'assistant') out.push({ role: 'assistant', content: e.payload })
153
- else if (e.kind === 'toolCall') out.push({ role: 'assistant', toolCall: e.payload })
172
+ else if (e.kind === 'toolCall') {
173
+ const msg: { role: string; [k: string]: unknown } = { role: 'assistant', toolCall: e.payload }
174
+ if (pendingThinking) {
175
+ msg.thinking = {
176
+ text: pendingThinking.text,
177
+ ...(pendingThinking.signature ? { signature: pendingThinking.signature } : {}),
178
+ ...(pendingThinking.redacted ? { redacted: true } : {}),
179
+ }
180
+ pendingThinking = null // 首 toolCall 消费即清——同 turn 多 toolCall = 同一原始消息,单块重放
181
+ }
182
+ out.push(msg)
183
+ }
154
184
  else if (e.kind === 'toolResult') {
155
185
  const id = (e.payload as { id?: unknown } | null)?.id
156
186
  if (typeof id === 'string' && lastSeqById.get(id) !== e.seq) continue // D-23:后续有同 id 最终结果,本条为授权中间记录
Binary file
@@ -1 +0,0 @@
1
- {"base64Signature":"MEUCIBP09C8Zaq0IyLxblaFx3lzHcTPG7VbHr9fnhH/1FijbAiEApJUnN0KBsXmmN7YnGoiCSLxATkO1heeQsrlkaPSCh8g=","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1akNDQm15Z0F3SUJBZ0lVUlI0MW81WkdEMy9DajBSdkdLMmhzSnNXV0hFd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1UQXhNRGswTlRNMFdoY05Nall4TVRBeE1EazFOVE0wV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVuZXB0SDdzdGxlL2h4ZHRLZ0l6Mm1MdHBmY1lhQVMrb05tWVAKUTM1WSs4aDQwdTRKcmhQeUIzdU56Y050RnpEWlIyRGU5SHlSYktRL1hBallhYTVwaXFPQ0JZc3dnZ1dITUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakU1TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLR1JqTm1Sak5EQTBZVGN4T0RkaU9UQXpOamRtWldNMwpaRFk1WXpaaVlqSmlPV0V5T1RObVpEZ3dGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNVGt1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakU1TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENoawpZelprWXpRd05HRTNNVGczWWprd016WTNabVZqTjJRMk9XTTJZbUl5WWpsaE1qa3pabVE0TUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENoawpZelprWXpRd05HRTNNVGczWWprd016WTNabVZqTjJRMk9XTTJZbUl5WWpsaE1qa3pabVE0TUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNVGt1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpFNUxqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2hrWXpaa1l6UXdOR0UzTVRnM1lqa3dNelkzWm1Wak4yUTJPV00yWW1JeVlqbGhNamt6Wm1RNE1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6UXhNamswTkRnMApOakV3TDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eE9TNHdNSUdKQmdvckJnRUVBZFo1QWdRQ0JIc0VlUUIzQUhVQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2hsb0FGM2dBQUJBTUFSakJFCkFpQjVjUGZEb0pVN1EvVTFmdHhtWWlGcyt4ZktQRXQ3ZVBhOWU4MFhaamRYMHdJZ1ZVY3E5UE4ydG03QUpMK08KamdmNVpFNVFvVnFSSW03RzE3Y0ZQUjdva0RVd0NnWUlLb1pJemowRUF3TURhQUF3WlFJeEFPMkNia0VTNmlDTQprUk4wbEZqUkxVVVdwZ1FrMkxQc3FmOTVteVZObDJQLzMvQUtXWTJkUmRnSTlLMThUZ2RGWVFJd1BQQ2VhZlRMCi9nNnpNSTc1bXFLY0ZFVmhqVlJJZnRyMHN0RlRTZkRkT1paR2UrcjFXN2dMUC81alREUTVHZ2ZkCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIQCOsnCzp5zMWX8CrzrAKjTznpryixWK9TKSOJ2EpPJZ9AIgWycT72YXDKqslN0wfQXBnY1Br76C62gxBCtgx5cF/kg=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0OWQxOGVhMmEzZDAwMWQzYzY3YzUzYmQ2M2U0MjIzOTNhOTQ0ZmI0ZGVjZTRmNTJjNzA5YzUwYWRlMmM0MGJlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQlAwOUM4WmFxMEl5THhibGFGeDNsekhjVFBHN1ZiSHI5Zm5oSC8xRmlqYkFpRUFwSlVuTjBLQnNYbW1ON1luR29pQ1NMeEFUa08xaGVlUXNybGthUFNDaDhnPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFha05EUW0xNVowRjNTVUpCWjBsVlVsSTBNVzgxV2tkRU15OURhakJTZGtkTE1taHpTbk5YVjBoRmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFVUVhoTlJHc3dUbFJOTUZkb1kwNU5hbGw0VFZSQmVFMUVhekZPVkUwd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ1WlhCMFNEZHpkR3hsTDJoNFpIUkxaMGw2TW0xTWRIQm1ZMWxoUVZNcmIwNXRXVkFLVVRNMVdTczRhRFF3ZFRSS2NtaFFlVUl6ZFU1NlkwNTBSbnBFV2xJeVJHVTVTSGxTWWt0UkwxaEJhbGxoWVRWd2FYRlBRMEpaYzNkbloxZElUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha1UxVEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUjFKcVRtMVNhazVFUVRCWlZHTjRUMFJrYVU5VVFYcE9hbVJ0V2xkTk13cGFSRmsxV1hwYWFWbHFTbWxQVjBWNVQxUk9iVnBFWjNkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOVkd0MVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrVTFUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm9hd3BaZWxwcldYcFJkMDVIUlROTlZHY3pXV3ByZDAxNldUTmFiVlpxVGpKUk1rOVhUVEpaYlVsNVdXcHNhRTFxYTNwYWJWRTBUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVOb2F3cFplbHByV1hwUmQwNUhSVE5OVkdjeldXcHJkMDE2V1ROYWJWWnFUakpSTWs5WFRUSlpiVWw1V1dwc2FFMXFhM3BhYlZFMFRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOVkd0MVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BGTlV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJocldYcGFhMWw2VVhkT1IwVXpUVlJuTTFscWEzZE5lbGt6V20xV2FrNHlVVEpQVjAweVdXMUplVmxxYkdoTmFtdDZXbTFSTkUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlVYaE5hbXN3VGtSbk1BcE9ha1YzVERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZUU5VE5IZE5TVWRLUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJYzBWbFVVSXpRVWhWUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaHNiMEZHTTJkQlFVSkJUVUZTYWtKRkNrRnBRalZqVUdaRWIwcFZOMUV2VlRGbWRIaHRXV2xHY3l0NFprdFFSWFEzWlZCaE9XVTRNRmhhYW1SWU1IZEpaMVpWWTNFNVVFNHlkRzAzUVVwTUswOEthbWRtTlZwRk5WRnZWbkZTU1cwM1J6RTNZMFpRVWpkdmEwUlZkME5uV1VsTGIxcEplbW93UlVGM1RVUmhRVUYzV2xGSmVFRlBNa05pYTBWVE5tbERUUXByVWs0d2JFWnFVa3hWVlZkd1oxRnJNa3hRYzNGbU9UVnRlVlpPYkRKUUx6TXZRVXRYV1RKa1VtUm5TVGxMTVRoVVoyUkdXVkZKZDFCUVEyVmhabFJNQ2k5bk5ucE5TVGMxYlhGTFkwWkZWbWhxVmxKSlpuUnlNSE4wUmxSVFprUmtUMXBhUjJVcmNqRlhOMmRNVUM4MWFsUkVVVFZIWjJaa0NpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19","integratedTime":1793526337,"logIndex":3625869466,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}