@lqc123qwe/car-runtime 1.10.0 → 1.11.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 +1 -1
- package/dist/authz/authz.d.ts +2 -0
- package/dist/authz/authz.js +11 -0
- package/dist/car-runtime-1.11.0.tgz +0 -0
- package/dist/car-runtime-1.11.0.tgz.sig.bundle +1 -0
- package/dist/loop/stop.d.ts +3 -0
- package/dist/loop/stop.js +43 -10
- package/dist/release-audit.jsonl +4 -4
- package/dist/runtime-core/chatStep.d.ts +4 -1
- package/dist/runtime-core/chatStep.js +17 -0
- package/dist/runtime-core/llm.js +97 -12
- package/dist/runtime-core/types.d.ts +20 -0
- package/dist/sbom.json +9 -9
- package/dist/seam.d.ts +3 -0
- package/dist/seam.js +3 -0
- package/dist/session/log.d.ts +1 -1
- package/package.json +1 -1
- package/src/authz/authz.ts +12 -0
- package/src/loop/stop.ts +43 -10
- package/src/runtime-core/chatStep.ts +22 -2
- package/src/runtime-core/llm.ts +96 -13
- package/src/runtime-core/types.ts +22 -1
- package/src/seam.ts +3 -0
- package/src/session/log.ts +1 -1
- package/dist/car-runtime-1.10.0.tgz +0 -0
- package/dist/car-runtime-1.10.0.tgz.sig.bundle +0 -1
package/dist/SHA256SUMS
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e33ef93a776d291064c40692465fcc09d4281343d4f5ef06d38989d9dc95922f car-runtime-1.11.0.tgz
|
package/dist/authz/authz.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare class AuthzService {
|
|
|
39
39
|
});
|
|
40
40
|
/** 声明式放行(policy):readonly 模式下写类一律 DENIED,无需人审 */
|
|
41
41
|
decideByPolicy(req: AuthzRequest, mode: 'readonly' | 'confirm' | 'full'): AuthzDecision;
|
|
42
|
+
/** 会话策略直判(「会话内始终允许」粒度):decidedBy=policy 幂等落链,不入人审通道(审批回调 scope=session 写入 grants 后走此路) */
|
|
43
|
+
decideBySessionPolicy(req: AuthzRequest): AuthzDecision;
|
|
42
44
|
/** 人审(confirm 模式):120s 超时默认拒绝(timeout-default-deny) */
|
|
43
45
|
decideByUser(req: AuthzRequest, approve: (req: AuthzRequest) => Promise<boolean>): Promise<AuthzDecision>;
|
|
44
46
|
}
|
package/dist/authz/authz.js
CHANGED
|
@@ -26,6 +26,17 @@ export class AuthzService {
|
|
|
26
26
|
void writeish;
|
|
27
27
|
return this.#commit(decision);
|
|
28
28
|
}
|
|
29
|
+
/** 会话策略直判(「会话内始终允许」粒度):decidedBy=policy 幂等落链,不入人审通道(审批回调 scope=session 写入 grants 后走此路) */
|
|
30
|
+
decideBySessionPolicy(req) {
|
|
31
|
+
return this.#commit({
|
|
32
|
+
authorizationId: req.authorizationId,
|
|
33
|
+
decision: 'APPROVED',
|
|
34
|
+
decidedBy: 'policy',
|
|
35
|
+
reason: `session policy grant (always-allow): ${req.resource}`,
|
|
36
|
+
degraded: this.#degraded,
|
|
37
|
+
ts: Date.now(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
29
40
|
/** 人审(confirm 模式):120s 超时默认拒绝(timeout-default-deny) */
|
|
30
41
|
async decideByUser(req, approve) {
|
|
31
42
|
const cached = this.#decisions.get(req.authorizationId);
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"base64Signature":"MEUCIBJoQ5rJ6ym7jD0dTnLKCFq7LU6CGJEe2ZC8VCzjZPELAiEA+kGBiQ/PYJKPCrZJ7szsJ70Fnff7/bn+lCzovNHu/MM=","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUc1ekNDQm02Z0F3SUJBZ0lVTThaMW1YN01CSjVNU1VpRndobFRhMkxYdm04d0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZeE1EQTNNRGcxTmpBM1doY05Nall4TURBM01Ea3dOakEzV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVlUkFjM0hsRTgzSXRxTnRBR1NlWVNJck5RMUczVWFGTWlYdzYKRmlBYXVvY0hxd2VCVllPeVptSko5SFdBcTZtd2ZhTC9lSlVxRHBBV2VzeHBHcG5pWktPQ0JZMHdnZ1dKTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWZCZ05WSFNNRUdEQVdnQlRmCjArblBWaVFSbHZtbzJPa29WYUxHTGhoa1B6QmlCZ05WSFJFQkFmOEVXREJXaGxSb2RIUndjem92TDJkcGRHaDEKWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHpMM0psYkdWaApjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV4TGpBd09RWUtLd1lCQkFHRHZ6QUJBUVFyYUhSMGNITTZMeTkwCmIydGxiaTVoWTNScGIyNXpMbWRwZEdoMVluVnpaWEpqYjI1MFpXNTBMbU52YlRBVkJnb3JCZ0VFQVlPL01BRUMKQkFkeVpXeGxZWE5sTURZR0Npc0dBUVFCZzc4d0FRTUVLRFUwT0RnNU5EbGxZelpoWmpFd1pqSTNNVGsxWm1VMwpZVFl3TURGa016SmxObVUyT0dVMk5EVXdGUVlLS3dZQkJBR0R2ekFCQkFRSGNtVnNaV0Z6WlRBZ0Jnb3JCZ0VFCkFZTy9NQUVGQkJKc2NXTTJNalF2WTJGeUxYSjFiblJwYldVd0hnWUtLd1lCQkFHRHZ6QUJCZ1FRY21WbWN5OTAKWVdkekx6RXVNVEV1TURBN0Jnb3JCZ0VFQVlPL01BRUlCQzBNSzJoMGRIQnpPaTh2ZEc5clpXNHVZV04wYVc5dQpjeTVuYVhSb2RXSjFjMlZ5WTI5dWRHVnVkQzVqYjIwd1pBWUtLd1lCQkFHRHZ6QUJDUVJXREZSb2RIUndjem92CkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMEwyTmhjaTF5ZFc1MGFXMWxMeTVuYVhSb2RXSXZkMjl5YTJac2IzZHoKTDNKbGJHVmhjMlV1ZVcxc1FISmxabk12ZEdGbmN5OHhMakV4TGpBd09BWUtLd1lCQkFHRHZ6QUJDZ1FxRENnMQpORGc0T1RRNVpXTTJZV1l4TUdZeU56RTVOV1psTjJFMk1EQXhaRE15WlRabE5qaGxOalExTUIwR0Npc0dBUVFCCmc3OHdBUXNFRHd3TloybDBhSFZpTFdodmMzUmxaREExQmdvckJnRUVBWU8vTUFFTUJDY01KV2gwZEhCek9pOHYKWjJsMGFIVmlMbU52YlM5c2NXTTJNalF2WTJGeUxYSjFiblJwYldVd09BWUtLd1lCQkFHRHZ6QUJEUVFxRENnMQpORGc0T1RRNVpXTTJZV1l4TUdZeU56RTVOV1psTjJFMk1EQXhaRE15WlRabE5qaGxOalExTUNBR0Npc0dBUVFCCmc3OHdBUTRFRWd3UWNtVm1jeTkwWVdkekx6RXVNVEV1TURBYUJnb3JCZ0VFQVlPL01BRVBCQXdNQ2pFek5qRXgKT1RJM01UUXdLUVlLS3dZQkJBR0R2ekFCRUFRYkRCbG9kSFJ3Y3pvdkwyZHBkR2gxWWk1amIyMHZiSEZqTmpJMApNQmdHQ2lzR0FRUUJnNzh3QVJFRUNnd0lOamt5TXpRMU5qUXdaQVlLS3dZQkJBR0R2ekFCRWdSV0RGUm9kSFJ3CmN6b3ZMMmRwZEdoMVlpNWpiMjB2YkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTHk1bmFYUm9kV0l2ZDI5eWEyWnMKYjNkekwzSmxiR1ZoYzJVdWVXMXNRSEpsWm5NdmRHRm5jeTh4TGpFeExqQXdPQVlLS3dZQkJBR0R2ekFCRXdRcQpEQ2cxTkRnNE9UUTVaV00yWVdZeE1HWXlOekU1TldabE4yRTJNREF4WkRNeVpUWmxOamhsTmpRMU1CY0dDaXNHCkFRUUJnNzh3QVJRRUNRd0hjbVZzWldGelpUQlpCZ29yQmdFRUFZTy9NQUVWQkVzTVNXaDBkSEJ6T2k4dloybDAKYUhWaUxtTnZiUzlzY1dNMk1qUXZZMkZ5TFhKMWJuUnBiV1V2WVdOMGFXOXVjeTl5ZFc1ekx6TTNOVGszTURrdwpOalEzTDJGMGRHVnRjSFJ6THpFd0ZnWUtLd1lCQkFHRHZ6QUJGZ1FJREFad2RXSnNhV013VUFZS0t3WUJCQUdECnZ6QUJHQVJDREVCeVpYQnZPbXh4WXpZeU5FQTJPVEl6TkRVMk5DOWpZWEl0Y25WdWRHbHRaVUF4TXpZeE1Ua3kKTnpFME9uSmxaanB5WldaekwzUmhaM012TVM0eE1TNHdNSUdMQmdvckJnRUVBZFo1QWdRQ0JIMEVld0I1QUhjQQozVDB3YXNiSEVUSmpHUjRjbVdjM0FxSktYcmplUEszL2g0cHlnQzhwN280QUFBR2hGWlBCSHdBQUJBTUFTREJHCkFpRUExaVV6NGxjb3BTbXpHdTJRWFlGeXhCS2pyOFA3MEVsWU1XMW4wYmNnKzIwQ0lRQ0YvUUZxUWV4clhFWDYKR0RKVzEwRUJuOVQ2OVlBMjZWM05FeElGdm8zeWpqQUtCZ2dxaGtqT1BRUURBd05uQURCa0FqQjRsMSs4S2RpbgpNcjhlaDBKMVE4b2JkT0RVSHVaUXB3cmM4cTM1b2ZGODQ2dHhJcnZyMlcweFlhNkNxb1JCcTZFQ01Ec1VyQ24vCkc4NFFURmVwaG16VnhYazNwMmRmcVhlVysyUDdWN200R2FEaDhuR1VNaHBXNWQ5aGVrYTk1RXJuV1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEYCIQDPLFWM6cbJ0BeT07DwUHQQPG8+WRftcTmRfg0JDi30ZQIhAOulABgNNSxNARfT+9V5QdCEh9M6830SCxriJs8m4kFh","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlMzNlZjkzYTc3NmQyOTEwNjRjNDA2OTI0NjVmY2MwOWQ0MjgxMzQzZDRmNWVmMDZkMzg5ODlkOWRjOTU5MjJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQkpvUTVySjZ5bTdqRDBkVG5MS0NGcTdMVTZDR0pFZTJaQzhWQ3pqWlBFTEFpRUEra0dCaVEvUFlKS1BDclpKN3N6c0o3MEZuZmY3L2JuK2xDem92Tkh1L01NPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVYzFla05EUW0wMlowRjNTVUpCWjBsVlRUaGFNVzFZTjAxQ1NqVk5VMVZwUm5kb2JGUmhNa3hZZG0wNGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFUVROTlJHY3hUbXBCTTFkb1kwNU5hbGw0VFVSQk0wMUVhM2RPYWtFelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZsVWtGak0waHNSVGd6U1hSeFRuUkJSMU5sV1ZOSmNrNVJNVWN6VldGR1RXbFlkellLUm1sQllYVnZZMGh4ZDJWQ1ZsbFBlVnB0U2tvNVNGZEJjVFp0ZDJaaFRDOWxTbFZ4UkhCQlYyVnplSEJIY0c1cFdrdFBRMEpaTUhkbloxZEtUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFtbENaMDVXU0ZKRlFrRm1PRVZYUkVKWGFHeFNiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmlTRVpxVG1wSk1Fd3lUbWhqYVRGNVpGYzFNR0ZYTVd4TWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhQXBqTWxWMVpWY3hjMUZJU214YWJrMTJaRWRHYm1ONU9IaE1ha1Y0VEdwQmQwOVJXVXRMZDFsQ1FrRkhSSFo2UVVKQlVWRnlZVWhTTUdOSVRUWk1lVGt3Q21JeWRHeGlhVFZvV1ROU2NHSXlOWHBNYldSd1pFZG9NVmx1Vm5wYVdFcHFZakkxTUZwWE5UQk1iVTUyWWxSQlZrSm5iM0pDWjBWRlFWbFBMMDFCUlVNS1FrRmtlVnBYZUd4WldFNXNUVVJaUjBOcGMwZEJVVkZDWnpjNGQwRlJUVVZMUkZVd1QwUm5OVTVFYkd4WmVscG9XbXBGZDFwcVNUTk5WR3N4V20xVk13cFpWRmwzVFVSR2EwMTZTbXhPYlZVeVQwZFZNazVFVlhkR1VWbExTM2RaUWtKQlIwUjJla0ZDUWtGUlNHTnRWbk5hVjBaNldsUkJaMEpuYjNKQ1owVkZDa0ZaVHk5TlFVVkdRa0pLYzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMGhuV1V0TGQxbENRa0ZIUkhaNlFVSkNaMUZSWTIxV2JXTjVPVEFLV1Zka2VreDZSWFZOVkVWMVRVUkJOMEpuYjNKQ1owVkZRVmxQTDAxQlJVbENRekJOU3pKb01HUklRbnBQYVRoMlpFYzVjbHBYTkhWWlYwNHdZVmM1ZFFwamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcVlqSXdkMXBCV1V0TGQxbENRa0ZIUkhaNlFVSkRVVkpYUkVaU2IyUklVbmRqZW05MkNrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUV3eVRtaGphVEY1WkZjMU1HRlhNV3hNZVRWdVlWaFNiMlJYU1haa01qbDVZVEphYzJJelpIb0tURE5LYkdKSFZtaGpNbFYxWlZjeGMxRklTbXhhYmsxMlpFZEdibU41T0hoTWFrVjRUR3BCZDA5QldVdExkMWxDUWtGSFJIWjZRVUpEWjFGeFJFTm5NUXBPUkdjMFQxUlJOVnBYVFRKWlYxbDRUVWRaZVU1NlJUVk9WMXBzVGpKRk1rMUVRWGhhUkUxNVdsUmFiRTVxYUd4T2FsRXhUVUl3UjBOcGMwZEJVVkZDQ21jM09IZEJVWE5GUkhkM1Rsb3liREJoU0ZacFRGZG9kbU16VW14YVJFRXhRbWR2Y2tKblJVVkJXVTh2VFVGRlRVSkRZMDFLVjJnd1pFaENlazlwT0hZS1dqSnNNR0ZJVm1sTWJVNTJZbE01YzJOWFRUSk5hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFZkMDlCV1V0TGQxbENRa0ZIUkhaNlFVSkVVVkZ4UkVObk1RcE9SR2MwVDFSUk5WcFhUVEpaVjFsNFRVZFplVTU2UlRWT1YxcHNUakpGTWsxRVFYaGFSRTE1V2xSYWJFNXFhR3hPYWxFeFRVTkJSME5wYzBkQlVWRkNDbWMzT0hkQlVUUkZSV2QzVVdOdFZtMWplVGt3V1Zka2VreDZSWFZOVkVWMVRVUkJZVUpuYjNKQ1owVkZRVmxQTDAxQlJWQkNRWGROUTJwRmVrNXFSWGdLVDFSSk0wMVVVWGRMVVZsTFMzZFpRa0pCUjBSMmVrRkNSVUZSWWtSQ2JHOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaVNFWnFUbXBKTUFwTlFtZEhRMmx6UjBGUlVVSm5OemgzUVZKRlJVTm5kMGxPYW10NVRYcFJNVTVxVVhkYVFWbExTM2RaUWtKQlIwUjJla0ZDUldkU1YwUkdVbTlrU0ZKM0NtTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2hHYWs1cVNUQk1NazVvWTJreGVXUlhOVEJoVnpGc1RIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTUtZak5rZWt3elNteGlSMVpvWXpKVmRXVlhNWE5SU0Vwc1dtNU5kbVJIUm01amVUaDRUR3BGZUV4cVFYZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JYZFJjUXBFUTJjeFRrUm5ORTlVVVRWYVYwMHlXVmRaZUUxSFdYbE9la1UxVGxkYWJFNHlSVEpOUkVGNFdrUk5lVnBVV214T2FtaHNUbXBSTVUxQ1kwZERhWE5IQ2tGUlVVSm5OemgzUVZKUlJVTlJkMGhqYlZaeldsZEdlbHBVUWxwQ1oyOXlRbWRGUlVGWlR5OU5RVVZXUWtWelRWTlhhREJrU0VKNlQyazRkbG95YkRBS1lVaFdhVXh0VG5aaVV6bHpZMWROTWsxcVVYWlpNa1o1VEZoS01XSnVVbkJpVjFWMldWZE9NR0ZYT1hWamVUbDVaRmMxZWt4NlRUTk9WR3N6VFVScmR3cE9hbEV6VERKR01HUkhWblJqU0ZKNlRIcEZkMFpuV1V0TGQxbENRa0ZIUkhaNlFVSkdaMUZKUkVGYWQyUlhTbk5oVjAxM1ZVRlpTMHQzV1VKQ1FVZEVDblo2UVVKSFFWSkRSRVZDZVZwWVFuWlBiWGg0V1hwWmVVNUZRVEpQVkVsNlRrUlZNazVET1dwWldFbDBZMjVXZFdSSGJIUmFWVUY0VFhwWmVFMVVhM2tLVG5wRk1FOXVTbXhhYW5CNVdsZGFla3d6VW1oYU0wMTJUVk0wZUUxVE5IZE5TVWRNUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJTUVWbGQwSTFRVWhqUVFvelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIyaEdXbEJDU0hkQlFVSkJUVUZUUkVKSENrRnBSVUV4YVZWNk5HeGpiM0JUYlhwSGRUSlJXRmxHZVhoQ1MycHlPRkEzTUVWc1dVMVhNVzR3WW1Obkt6SXdRMGxSUTBZdlVVWnhVV1Y0Y2xoRldEWUtSMFJLVnpFd1JVSnVPVlEyT1ZsQk1qWldNMDVGZUVsR2RtOHplV3BxUVV0Q1oyZHhhR3RxVDFCUlVVUkJkMDV1UVVSQ2EwRnFRalJzTVNzNFMyUnBiZ3BOY2pobGFEQktNVkU0YjJKa1QwUlZTSFZhVVhCM2NtTTRjVE0xYjJaR09EUTJkSGhKY25aeU1sY3dlRmxoTmtOeGIxSkNjVFpGUTAxRWMxVnlRMjR2Q2tjNE5GRlVSbVZ3YUcxNlZuaFlhek53TW1SbWNWaGxWeXN5VURkV04yMDBSMkZFYURodVIxVk5hSEJYTldRNWFHVnJZVGsxUlhKdVYxRTlQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=","integratedTime":1791363367,"logIndex":3127715428,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
|
package/dist/loop/stop.d.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* turn 内任一 step 撞限 → turn/end 保留 max-tokens,不被后续成功 step 冲销
|
|
11
11
|
* - 工具收口:concludesTurn=OR(任一 true 即收口);terminate=AND(整批 finalized 全 true 才中断)
|
|
12
12
|
* - 取消:未派发调用补记成对事件(toolCall + 合成错误 toolResult),日志无缺口
|
|
13
|
+
* - 1.11-S2(D-33 / 计量设计 §3.3):turnEnd meta.usage = 本 turn usage 事件四分量求和
|
|
14
|
+
* (聚合在内核消费侧单点 closeTurn——适配器无状态 ACL;无 usage 事件 → meta 不带 usage 键,
|
|
15
|
+
* 缺省=未采集向后兼容;撞限/异常 turn 照常聚合,截断前已完成请求的 usage 事实不吞)
|
|
13
16
|
*/
|
|
14
17
|
import type { SessionLog } from '../session/log.ts';
|
|
15
18
|
export type StepOutcome = 'completed' | 'max-tokens' | 'null';
|
package/dist/loop/stop.js
CHANGED
|
@@ -2,12 +2,45 @@ import { CarM8Error } from '../runtime-core/errors.js';
|
|
|
2
2
|
export class TurnAborted extends Error {
|
|
3
3
|
constructor() { super('CAR-ABORTED: turn cancelled by user'); }
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* 1.11-S2(D-33 / 计量设计 §3.3):turn 级 usage 聚合——扫描本 turn 的 'usage' 事件(事实本体
|
|
7
|
+
* 在 JSONL,聚合由事实行求和而非运行时状态;字段非数值容错跳过不吞计数)。无 usage 事件
|
|
8
|
+
* 返回 undefined → turnEnd meta 不带 usage 键(缺省=未采集,旧格式向后兼容)。
|
|
9
|
+
*/
|
|
10
|
+
function turnUsageFrom(log, turnId) {
|
|
11
|
+
let input = 0;
|
|
12
|
+
let output = 0;
|
|
13
|
+
let cacheRead = 0;
|
|
14
|
+
let cacheCreation = 0;
|
|
15
|
+
let requests = 0;
|
|
16
|
+
for (const e of log.events) {
|
|
17
|
+
if (e.kind !== 'usage' || e.turnId !== turnId)
|
|
18
|
+
continue;
|
|
19
|
+
const p = (e.payload ?? {});
|
|
20
|
+
if (typeof p.input === 'number')
|
|
21
|
+
input += p.input;
|
|
22
|
+
if (typeof p.output === 'number')
|
|
23
|
+
output += p.output;
|
|
24
|
+
if (typeof p.cacheRead === 'number')
|
|
25
|
+
cacheRead += p.cacheRead;
|
|
26
|
+
if (typeof p.cacheCreation === 'number')
|
|
27
|
+
cacheCreation += p.cacheCreation;
|
|
28
|
+
requests++;
|
|
29
|
+
}
|
|
30
|
+
return requests ? { input, output, cacheRead, cacheCreation, requests } : undefined;
|
|
31
|
+
}
|
|
5
32
|
export async function runTurn(opts) {
|
|
6
33
|
const { log, turnId, model, tools, preset = {}, signal } = opts;
|
|
7
34
|
const maxSteps = preset.maxSteps ?? 32;
|
|
8
35
|
const trail = [];
|
|
9
36
|
let sawMaxTokens = false;
|
|
10
37
|
let steps = 0;
|
|
38
|
+
// 1.11-S2(D-33):turnEnd 落链单点包装——聚合本 turn usage 事件(无 usage 不带键,
|
|
39
|
+
// 缺省=未采集向后兼容);reason 保持 meta 首键,usage 恒为加法尾键
|
|
40
|
+
const closeTurn = (meta) => {
|
|
41
|
+
const usage = turnUsageFrom(log, turnId);
|
|
42
|
+
return log.append('runtime', 'turnEnd', turnId, null, { ...meta, ...(usage ? { usage } : {}) });
|
|
43
|
+
};
|
|
11
44
|
const executeBatch = async (calls) => {
|
|
12
45
|
let concludesTurn = false;
|
|
13
46
|
let concludesVotes = 0;
|
|
@@ -65,7 +98,7 @@ export async function runTurn(opts) {
|
|
|
65
98
|
try {
|
|
66
99
|
while (steps < maxSteps) {
|
|
67
100
|
if (signal?.aborted) {
|
|
68
|
-
const seq =
|
|
101
|
+
const seq = closeTurn({ reason: 'aborted' }).seq;
|
|
69
102
|
trail.push({ seq, reason: 'aborted' });
|
|
70
103
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
71
104
|
}
|
|
@@ -73,7 +106,7 @@ export async function runTurn(opts) {
|
|
|
73
106
|
log.snapshotModelRequest(); // agent-loop 集成点:请求时快照(N1)
|
|
74
107
|
const step = await model();
|
|
75
108
|
if (step.stopReason === 'error') {
|
|
76
|
-
const seq =
|
|
109
|
+
const seq = closeTurn({ reason: 'error' }).seq;
|
|
77
110
|
trail.push({ seq, reason: 'error' });
|
|
78
111
|
return { reason: 'error', steps, endReasonTrail: trail };
|
|
79
112
|
}
|
|
@@ -92,7 +125,7 @@ export async function runTurn(opts) {
|
|
|
92
125
|
continue;
|
|
93
126
|
}
|
|
94
127
|
// 默认收口:截断响应不解析、不执行任何工具调用
|
|
95
|
-
const seq =
|
|
128
|
+
const seq = closeTurn({ reason: 'max-tokens' }).seq;
|
|
96
129
|
trail.push({ seq, reason: 'max-tokens' });
|
|
97
130
|
return { reason: 'max-tokens', steps, endReasonTrail: trail };
|
|
98
131
|
}
|
|
@@ -100,21 +133,21 @@ export async function runTurn(opts) {
|
|
|
100
133
|
const batch = await executeBatch(step.toolCalls);
|
|
101
134
|
// F13:授权拒绝即收口(blockOnDeny 产出点——blocked 仅新增产出点,枚举不增)
|
|
102
135
|
if (preset.blockOnDeny && batch.denies > 0) {
|
|
103
|
-
const seq =
|
|
136
|
+
const seq = closeTurn({ reason: 'blocked', denies: batch.denies }).seq;
|
|
104
137
|
trail.push({ seq, reason: 'blocked' });
|
|
105
138
|
return { reason: 'blocked', steps, endReasonTrail: trail };
|
|
106
139
|
}
|
|
107
140
|
const agg = preset.aggregate ?? {};
|
|
108
141
|
const abortedNow = (agg.terminate ?? 'all') === 'any' ? batch.terminateVotes > 0 : batch.terminateAll;
|
|
109
142
|
if (abortedNow) { // terminate 收口:默认 AND(整批),可配置 any
|
|
110
|
-
const seq =
|
|
143
|
+
const seq = closeTurn({ reason: 'aborted' }).seq;
|
|
111
144
|
trail.push({ seq, reason: 'aborted' });
|
|
112
145
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
113
146
|
}
|
|
114
147
|
const concludedNow = (agg.concludesTurn ?? 'any') === 'any' ? batch.concludesTurn : batch.concludesAll && batch.finalized > 0;
|
|
115
148
|
if (concludedNow) { // concludesTurn 收口:默认 OR,可配置 all
|
|
116
149
|
const reason = sawMaxTokens ? 'max-tokens' : 'completed';
|
|
117
|
-
const seq =
|
|
150
|
+
const seq = closeTurn({ reason }).seq;
|
|
118
151
|
trail.push({ seq, reason });
|
|
119
152
|
return { reason, steps, endReasonTrail: trail };
|
|
120
153
|
}
|
|
@@ -123,17 +156,17 @@ export async function runTurn(opts) {
|
|
|
123
156
|
}
|
|
124
157
|
// stopReason === 'stop':模型自然完成
|
|
125
158
|
const reason = sawMaxTokens ? 'max-tokens' : 'completed';
|
|
126
|
-
const seq =
|
|
159
|
+
const seq = closeTurn({ reason }).seq;
|
|
127
160
|
trail.push({ seq, reason });
|
|
128
161
|
return { reason, steps, endReasonTrail: trail };
|
|
129
162
|
}
|
|
130
|
-
const seq =
|
|
163
|
+
const seq = closeTurn({ reason: 'error', detail: 'max-steps' }).seq;
|
|
131
164
|
trail.push({ seq, reason: 'error' });
|
|
132
165
|
return { reason: 'error', steps, endReasonTrail: trail };
|
|
133
166
|
}
|
|
134
167
|
catch (e) {
|
|
135
168
|
if (e instanceof TurnAborted) {
|
|
136
|
-
const seq =
|
|
169
|
+
const seq = closeTurn({ reason: 'aborted' }).seq;
|
|
137
170
|
trail.push({ seq, reason: 'aborted' });
|
|
138
171
|
return { reason: 'aborted', steps, endReasonTrail: trail };
|
|
139
172
|
}
|
|
@@ -141,7 +174,7 @@ export async function runTurn(opts) {
|
|
|
141
174
|
// car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变)
|
|
142
175
|
const m8 = e instanceof CarM8Error;
|
|
143
176
|
const detail = m8 ? `${e.slug}: ${e.message}` : String(e);
|
|
144
|
-
const seq =
|
|
177
|
+
const seq = closeTurn({
|
|
145
178
|
reason: 'error', detail,
|
|
146
179
|
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
|
|
147
180
|
}).seq;
|
package/dist/release-audit.jsonl
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{"stage":"archive","detail":"git archive HEAD → car-runtime-1.
|
|
2
|
-
{"stage":"sha256sums","detail":"SHA256SUMS
|
|
3
|
-
{"stage":"sbom","detail":"51 个组件(运行时依赖 0 项)","ts":
|
|
4
|
-
{"stage":"cosign-keyless","detail":"signed car-runtime-1.
|
|
1
|
+
{"stage":"archive","detail":"git archive HEAD → car-runtime-1.11.0.tgz(328238 bytes,树哈希锚定)","ts":1791363366364}
|
|
2
|
+
{"stage":"sha256sums","detail":"SHA256SUMS e33ef93a776d2910…(裸字节复核通过)","ts":1791363366365}
|
|
3
|
+
{"stage":"sbom","detail":"51 个组件(运行时依赖 0 项)","ts":1791363366373}
|
|
4
|
+
{"stage":"cosign-keyless","detail":"signed car-runtime-1.11.0.tgz (keyless OIDC + Rekor)","ts":1791363368159}
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
* (半截 JSON 不解析——ADR-001 同纪律,length 时只交 truncatedTools);
|
|
13
13
|
* 5. assistant 文本事件落 M7(脱敏后 + secretsRedacted 计数留痕 meta);
|
|
14
14
|
* 6. finishReason 不可变透传为 ModelStep.stopReason(error/aborted → 'error',BD-04 收口
|
|
15
|
-
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch
|
|
15
|
+
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch 落(本层不重复);
|
|
16
|
+
* 7. 1.11-S2(D-33 / 计量设计 §3.3):流尾 usage chunk → 'runtime'/'usage' 请求级事实落链
|
|
17
|
+
* (缺失 = provider 未回/流中断/畸形,禁补造;actor='runtime'、model_visible=0 不进模型
|
|
18
|
+
* 消息流)——turn 级聚合在 stop.ts 收口单点,本层不做跨请求状态。
|
|
16
19
|
*/
|
|
17
20
|
import type { SessionLog } from '../session/log.ts';
|
|
18
21
|
import { type ModelStep } from '../loop/stop.ts';
|
|
@@ -27,6 +27,7 @@ export async function chatStep(opts) {
|
|
|
27
27
|
const calls = new Map();
|
|
28
28
|
let stopReason;
|
|
29
29
|
let errorDetail;
|
|
30
|
+
let usage;
|
|
30
31
|
const stream = core.chat({
|
|
31
32
|
model,
|
|
32
33
|
messages,
|
|
@@ -55,6 +56,8 @@ export async function chatStep(opts) {
|
|
|
55
56
|
}
|
|
56
57
|
if (chunk.error)
|
|
57
58
|
errorDetail = chunk.error.message;
|
|
59
|
+
if (chunk.usage)
|
|
60
|
+
usage = chunk.usage; // D-33:附着于最后一个 chunk——后到覆盖
|
|
58
61
|
if (chunk.finishReason !== undefined)
|
|
59
62
|
stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason;
|
|
60
63
|
}
|
|
@@ -67,6 +70,20 @@ export async function chatStep(opts) {
|
|
|
67
70
|
throw new Error('CAR-E-LLM-FINISH: 流结束无 finishReason(适配器契约违规)');
|
|
68
71
|
}
|
|
69
72
|
text += redactor.flush();
|
|
73
|
+
// 1.11-S2(D-33 / 计量设计 §3.3):请求级 usage 事实落链(拿到 usage 的最后一个 chunk 即请求
|
|
74
|
+
// 收尾;撞限/异常 turn 照常落——截断前已完成请求的 usage 事实不吞)。缺失 = provider 未回,
|
|
75
|
+
// 禁补造;actor='runtime'、不进模型消息流(deriveMessages 不投影 usage,§2.5 红线)
|
|
76
|
+
if (usage) {
|
|
77
|
+
log.append('runtime', 'usage', turnId, {
|
|
78
|
+
model: usage.model,
|
|
79
|
+
adapterId: usage.adapterId,
|
|
80
|
+
input: usage.inputTokens,
|
|
81
|
+
output: usage.outputTokens,
|
|
82
|
+
cacheRead: usage.cacheReadTokens,
|
|
83
|
+
cacheCreation: usage.cacheCreationTokens,
|
|
84
|
+
providerRaw: usage.providerRaw,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
70
87
|
// 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
|
|
71
88
|
// flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
|
|
72
89
|
let secretsRedacted = 0;
|
package/dist/runtime-core/llm.js
CHANGED
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
* 0xC0000409)——car run 真路径 ≥3 次模型请求 + PTC worker/MCP 子进程并存即确定性复现
|
|
21
21
|
* (1.4 彼时仅修遥测出站,LLM 出站同为 undici 默认——潜伏至 1.8 E2E 三请求形态暴露);
|
|
22
22
|
* 流式契约 body = AsyncIterable<Uint8Array> 不变;测试注入缝 fetchImpl 保留。
|
|
23
|
+
* - 1.11-S1(D-33 / 计量设计 §3.2):usage 采集——openai 请求体加 stream_options.include_usage
|
|
24
|
+
* (默认开启,第三方网关不兼容时回退 = baseUrl 白名单,登记可回退项;缺失走缺失口径禁估算),
|
|
25
|
+
* 解析「先摘 evt.usage 再判 choice」(空 choices 尾包不再被吞);anthropic message_start(input 侧)
|
|
26
|
+
* + message_delta(output 侧)两包合并(message_delta 缺席 = output 不完整,整条不落);
|
|
27
|
+
* 归一口径 §1.1(openai 减法 / anthropic 透传 + cache_creation),畸形/缺失整条不落(禁补造)。
|
|
23
28
|
*/
|
|
24
29
|
import { providerUnreachable } from './errors.js';
|
|
25
30
|
import { CredentialService } from './credentials.js';
|
|
@@ -255,8 +260,31 @@ const FINISH_MAP = {
|
|
|
255
260
|
};
|
|
256
261
|
class Retryable extends Error {
|
|
257
262
|
}
|
|
258
|
-
/**
|
|
259
|
-
|
|
263
|
+
/**
|
|
264
|
+
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
265
|
+
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
266
|
+
* 登记不估算)。usage 对象缺席/畸形(两侧主字段任一非数值)= 整条不落(缺失口径,禁补造)。
|
|
267
|
+
*/
|
|
268
|
+
function normalizeOpenAIUsage(u, model, adapterId) {
|
|
269
|
+
if (!u || typeof u !== 'object')
|
|
270
|
+
return undefined;
|
|
271
|
+
const r = u;
|
|
272
|
+
if (typeof r.prompt_tokens !== 'number' || typeof r.completion_tokens !== 'number')
|
|
273
|
+
return undefined;
|
|
274
|
+
const details = (r.prompt_tokens_details ?? null);
|
|
275
|
+
const cached = details && typeof details.cached_tokens === 'number' ? details.cached_tokens : 0;
|
|
276
|
+
return {
|
|
277
|
+
inputTokens: r.prompt_tokens - cached,
|
|
278
|
+
outputTokens: r.completion_tokens,
|
|
279
|
+
cacheReadTokens: cached,
|
|
280
|
+
cacheCreationTokens: 0,
|
|
281
|
+
model,
|
|
282
|
+
adapterId,
|
|
283
|
+
providerRaw: u,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** 明文 SSE → LlmChunk 流解析(增量容错:半行驻留到下一 chunk;1.11-S1:model/adapterId 供 usage 归一) */
|
|
287
|
+
async function* parseSseStream(body, model, adapterId) {
|
|
260
288
|
const decoder = new TextDecoder();
|
|
261
289
|
let buf = '';
|
|
262
290
|
for await (const bytes of body) {
|
|
@@ -279,9 +307,16 @@ async function* parseSseStream(body) {
|
|
|
279
307
|
catch {
|
|
280
308
|
continue;
|
|
281
309
|
} // 心跳/注释行容错(显式跳过非 JSON data)
|
|
310
|
+
// 1.11-S1(D-33 / 计量设计 §3.2 必核行):先摘 usage 再判 choice——stream_options.include_usage
|
|
311
|
+
// 的尾包 choices 为空,下方 `if (!choice) continue` 原样会把它吞掉
|
|
312
|
+
const usage = evt.usage !== undefined ? normalizeOpenAIUsage(evt.usage, model, adapterId) : undefined;
|
|
282
313
|
const choice = evt.choices?.[0];
|
|
283
|
-
if (!choice)
|
|
314
|
+
if (!choice) {
|
|
315
|
+
// 独立 usage 尾包(官方形态:finish_reason chunk 之后、[DONE] 之前)——请求最后一个 chunk
|
|
316
|
+
if (usage)
|
|
317
|
+
yield { usage };
|
|
284
318
|
continue;
|
|
319
|
+
}
|
|
285
320
|
const delta = choice.delta ?? {};
|
|
286
321
|
const raw = choice.finish_reason;
|
|
287
322
|
const finish = raw == null ? undefined : (() => {
|
|
@@ -300,28 +335,38 @@ async function* parseSseStream(body) {
|
|
|
300
335
|
: [];
|
|
301
336
|
const text = typeof delta.content === 'string' && delta.content.length ? delta.content : undefined;
|
|
302
337
|
// LlmChunk 单 toolCallDelta 口径:同事件多条 tool_calls 顺序展开为多 chunk
|
|
303
|
-
if (!text && !toolCallDeltas.length && finish === undefined)
|
|
338
|
+
if (!text && !toolCallDeltas.length && finish === undefined) {
|
|
339
|
+
// 空事件但携 usage(部分 compat 网关形态):usage 不随空事件丢失
|
|
340
|
+
if (usage)
|
|
341
|
+
yield { usage };
|
|
304
342
|
continue;
|
|
343
|
+
}
|
|
344
|
+
const chunks = [];
|
|
305
345
|
if (toolCallDeltas.length === 0) {
|
|
306
|
-
|
|
346
|
+
chunks.push({
|
|
307
347
|
...(text !== undefined ? { delta: text } : {}),
|
|
308
348
|
...(finish !== undefined
|
|
309
349
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
310
350
|
: {}),
|
|
311
|
-
};
|
|
351
|
+
});
|
|
312
352
|
}
|
|
313
353
|
else {
|
|
314
354
|
for (let i = 0; i < toolCallDeltas.length; i++) {
|
|
315
355
|
const last = i === toolCallDeltas.length - 1;
|
|
316
|
-
|
|
356
|
+
chunks.push({
|
|
317
357
|
...(i === 0 && text !== undefined ? { delta: text } : {}),
|
|
318
358
|
toolCallDelta: toolCallDeltas[i],
|
|
319
359
|
...(last && finish !== undefined
|
|
320
360
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
321
361
|
: {}),
|
|
322
|
-
};
|
|
362
|
+
});
|
|
323
363
|
}
|
|
324
364
|
}
|
|
365
|
+
// 1.11-S1(D-33):usage 与事件内容同 chunk 到达(网关共存形态)→ 附着于本事件最后一个 chunk
|
|
366
|
+
if (usage)
|
|
367
|
+
chunks[chunks.length - 1].usage = usage;
|
|
368
|
+
for (const c of chunks)
|
|
369
|
+
yield c;
|
|
325
370
|
}
|
|
326
371
|
}
|
|
327
372
|
}
|
|
@@ -349,6 +394,9 @@ export function createOpenAICompatAdapter(opts) {
|
|
|
349
394
|
body: JSON.stringify({
|
|
350
395
|
model: req.model,
|
|
351
396
|
stream: true,
|
|
397
|
+
// 1.11-S1(D-33 / 计量设计 §9-2 推荐A):usage 采集默认开启;第三方网关不兼容时的
|
|
398
|
+
// 回退方案(baseUrl 白名单)登记可回退项——不支持网关走缺失口径(禁估算补造)
|
|
399
|
+
stream_options: { include_usage: true },
|
|
352
400
|
...(req.maxTokens != null ? { max_tokens: req.maxTokens } : {}),
|
|
353
401
|
messages: req.messages.map(m => {
|
|
354
402
|
if (m.role === 'toolResult') {
|
|
@@ -386,7 +434,7 @@ export function createOpenAICompatAdapter(opts) {
|
|
|
386
434
|
fetchImpl,
|
|
387
435
|
signal: req.signal,
|
|
388
436
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
389
|
-
}, parseSseStream);
|
|
437
|
+
}, (body) => parseSseStream(body, req.model, id));
|
|
390
438
|
},
|
|
391
439
|
};
|
|
392
440
|
}
|
|
@@ -398,6 +446,27 @@ const ANTHROPIC_STOP_REASON_MAP = {
|
|
|
398
446
|
tool_use: 'toolUse',
|
|
399
447
|
refusal: 'aborted',
|
|
400
448
|
};
|
|
449
|
+
/**
|
|
450
|
+
* 1.11-S1(D-33 / 计量设计 §3.2):anthropic usage 两包合并——message_start(input 侧:
|
|
451
|
+
* input_tokens 天然不含 cache + cache_read/cache_creation)+ message_delta(output 侧:
|
|
452
|
+
* output_tokens)。任一包缺席或主字段非数值 = 侧不完整,整条不落(缺失口径,禁补造半条事实);
|
|
453
|
+
* providerRaw 保留两原始片段供审计对拍。
|
|
454
|
+
*/
|
|
455
|
+
function mergeAnthropicUsage(start, delta, model, adapterId) {
|
|
456
|
+
if (!start || !delta)
|
|
457
|
+
return undefined;
|
|
458
|
+
if (typeof start.input_tokens !== 'number' || typeof delta.output_tokens !== 'number')
|
|
459
|
+
return undefined;
|
|
460
|
+
return {
|
|
461
|
+
inputTokens: start.input_tokens,
|
|
462
|
+
outputTokens: delta.output_tokens,
|
|
463
|
+
cacheReadTokens: typeof start.cache_read_input_tokens === 'number' ? start.cache_read_input_tokens : 0,
|
|
464
|
+
cacheCreationTokens: typeof start.cache_creation_input_tokens === 'number' ? start.cache_creation_input_tokens : 0,
|
|
465
|
+
model,
|
|
466
|
+
adapterId,
|
|
467
|
+
providerRaw: { messageStart: start, messageDelta: delta },
|
|
468
|
+
};
|
|
469
|
+
}
|
|
401
470
|
/**
|
|
402
471
|
* Anthropic Messages API 请求体(D-20):system 消息提升为顶层 `system`(多条 \n\n 连接);
|
|
403
472
|
* toolResult 收敛进 user 消息 content(连续多条合并为单 user 多 tool_result 块——Anthropic
|
|
@@ -462,10 +531,13 @@ function toAnthropic(req, apiKey) {
|
|
|
462
531
|
* Anthropic SSE → LlmChunk 流解析(data-only:data JSON 自带 `type` 字段,`event:` 行不依赖)。
|
|
463
532
|
* content_block_start(tool_use) → toolCallDelta{id,name};input_json_delta → toolCallDelta{argumentsDelta};
|
|
464
533
|
* message_delta.stop_reason → finishReason(终态信号,单次);ping/message_stop/未知事件容错跳过。
|
|
534
|
+
* 1.11-S1(D-33):message_start.usage 采集 input 侧(原解析整体跳过该事件)+ message_delta.usage
|
|
535
|
+
* 合并 output 侧附着于终态 chunk;流中断 delta 未到 = output 侧不完整,整条不落(R-2)。
|
|
465
536
|
*/
|
|
466
|
-
async function* parseAnthropicSseStream(body) {
|
|
537
|
+
async function* parseAnthropicSseStream(body, model, adapterId) {
|
|
467
538
|
const decoder = new TextDecoder();
|
|
468
539
|
let buf = '';
|
|
540
|
+
let startUsage = null;
|
|
469
541
|
for await (const bytes of body) {
|
|
470
542
|
buf += decoder.decode(bytes, { stream: true });
|
|
471
543
|
let nl;
|
|
@@ -501,13 +573,26 @@ async function* parseAnthropicSseStream(body) {
|
|
|
501
573
|
}
|
|
502
574
|
// thinking_delta / citations_delta 等非 CAR 消费面:容错跳过
|
|
503
575
|
}
|
|
576
|
+
else if (type === 'message_start') {
|
|
577
|
+
// 1.11-S1(D-33):input 侧采集(usage 嵌套在 message 下)
|
|
578
|
+
const u = evt.message?.usage;
|
|
579
|
+
if (u && typeof u === 'object')
|
|
580
|
+
startUsage = u;
|
|
581
|
+
}
|
|
504
582
|
else if (type === 'message_delta') {
|
|
505
583
|
const raw = evt.delta?.stop_reason;
|
|
584
|
+
// 1.11-S1(D-33):output 侧(delta.usage.output_tokens)与 input 侧合并——两包齐才落
|
|
585
|
+
const usage = mergeAnthropicUsage(startUsage, (evt.usage ?? null), model, adapterId);
|
|
506
586
|
if (raw != null) {
|
|
507
587
|
const finish = ANTHROPIC_STOP_REASON_MAP[String(raw)];
|
|
508
|
-
|
|
588
|
+
const chunk = finish
|
|
509
589
|
? { finishReason: finish }
|
|
510
590
|
: { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"` } };
|
|
591
|
+
yield usage ? { ...chunk, usage } : chunk;
|
|
592
|
+
}
|
|
593
|
+
else if (usage) {
|
|
594
|
+
// delta 未携 stop_reason 但携 usage(网关形态):usage 不随缺失终态信号丢失
|
|
595
|
+
yield { usage };
|
|
511
596
|
}
|
|
512
597
|
}
|
|
513
598
|
else if (type === 'error') {
|
|
@@ -547,7 +632,7 @@ export function createAnthropicAdapter(opts) {
|
|
|
547
632
|
fetchImpl,
|
|
548
633
|
signal: req.signal,
|
|
549
634
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
550
|
-
}, parseAnthropicSseStream);
|
|
635
|
+
}, (body) => parseAnthropicSseStream(body, req.model, id));
|
|
551
636
|
},
|
|
552
637
|
};
|
|
553
638
|
}
|
|
@@ -41,6 +41,24 @@ export interface ToolCallDelta {
|
|
|
41
41
|
/** JSON 增量片段,聚合后整体 JSON.parse(半截 JSON 不解析——ADR-001 同纪律) */
|
|
42
42
|
argumentsDelta?: string;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 1.11-S1(D-33 / 计量设计 §3.1):token 四分量归一化用量——跨 provider 可加总的库内口径
|
|
46
|
+
* (§1.1:openai prompt_tokens 含 cached 必须减法归一;anthropic input_tokens 天然不含 cache)。
|
|
47
|
+
* provider 原始 usage 整体存 providerRaw 供审计对拍(对拍恒以 provider_raw 为准)。
|
|
48
|
+
*/
|
|
49
|
+
export interface LlmUsage {
|
|
50
|
+
/** 非缓存输入 token(归一化后) */
|
|
51
|
+
inputTokens: number;
|
|
52
|
+
outputTokens: number;
|
|
53
|
+
cacheReadTokens: number;
|
|
54
|
+
/** openai 系无对应字段,恒 0(登记不估算) */
|
|
55
|
+
cacheCreationTokens: number;
|
|
56
|
+
/** 实际请求模型(req.model) */
|
|
57
|
+
model: string;
|
|
58
|
+
adapterId: string;
|
|
59
|
+
/** provider 原始 usage 对象(anthropic 为 messageStart/messageDelta 两原始片段) */
|
|
60
|
+
providerRaw: unknown;
|
|
61
|
+
}
|
|
44
62
|
/** 流式块(finishReason 一经产生不可被任何中间层改写) */
|
|
45
63
|
export interface LlmChunk {
|
|
46
64
|
delta?: string;
|
|
@@ -51,6 +69,8 @@ export interface LlmChunk {
|
|
|
51
69
|
code: string;
|
|
52
70
|
message: string;
|
|
53
71
|
};
|
|
72
|
+
/** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
|
|
73
|
+
usage?: LlmUsage;
|
|
54
74
|
}
|
|
55
75
|
/** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
|
|
56
76
|
export interface LlmAdapter {
|
package/dist/sbom.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CAR-SBOM",
|
|
3
3
|
"specVersion": "0.1",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.11.0",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
7
7
|
"type": "file",
|
|
8
8
|
"path": "src/authz/authz.ts",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "93c9d92387109bde25cd33f4125a9cef19e74d9abedc74a0f5efb94552c1b472"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"type": "file",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
{
|
|
102
102
|
"type": "file",
|
|
103
103
|
"path": "src/loop/stop.ts",
|
|
104
|
-
"sha256": "
|
|
104
|
+
"sha256": "f32392a1047da2351c2430659a1e54f2701c7e6cef0015d456e7c8f984472057"
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"type": "file",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
{
|
|
152
152
|
"type": "file",
|
|
153
153
|
"path": "src/runtime-core/chatStep.ts",
|
|
154
|
-
"sha256": "
|
|
154
|
+
"sha256": "7a0f455e352529ee7e97a3ed2b9b7b9032493baae7542396d6f8b186fb788ecb"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
"type": "file",
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
{
|
|
167
167
|
"type": "file",
|
|
168
168
|
"path": "src/runtime-core/llm.ts",
|
|
169
|
-
"sha256": "
|
|
169
|
+
"sha256": "781b951a4e822c759fc7af0297679d4f000270162e3c3b1ff119c03e59e91cce"
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
"type": "file",
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
{
|
|
192
192
|
"type": "file",
|
|
193
193
|
"path": "src/runtime-core/types.ts",
|
|
194
|
-
"sha256": "
|
|
194
|
+
"sha256": "f828cc38072b36ba47d8a4f24337277fe5e5867fa0292a24696c9f845f204571"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
"type": "file",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
{
|
|
202
202
|
"type": "file",
|
|
203
203
|
"path": "src/seam.ts",
|
|
204
|
-
"sha256": "
|
|
204
|
+
"sha256": "79b27945adbc1784d2d76064878c16fabd229bee2fa533151e84cddd93712169"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"type": "file",
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
{
|
|
237
237
|
"type": "file",
|
|
238
238
|
"path": "src/session/log.ts",
|
|
239
|
-
"sha256": "
|
|
239
|
+
"sha256": "aa8c7b8d9cff690dfe7c6429399d9d2bd656f1ff1c07154715e4dc5d07946c9d"
|
|
240
240
|
},
|
|
241
241
|
{
|
|
242
242
|
"type": "file",
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
{
|
|
252
252
|
"type": "file",
|
|
253
253
|
"path": "package.json",
|
|
254
|
-
"sha256": "
|
|
254
|
+
"sha256": "56c8ff1fbee334a0829bcaf344aabebc3a58154282c002f7e6a215c9162254a8"
|
|
255
255
|
},
|
|
256
256
|
{
|
|
257
257
|
"type": "file",
|
package/dist/seam.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* - 仅暴露进程内挂接所需的最窄面(装配/会话/turn/遥测),禁止透出内核内部模块路径语义;
|
|
6
6
|
* - 破坏性变更须内核 major;加法演进走 minor 并在本文件头登记;
|
|
7
7
|
* - 语义不变:runTurn/chatStep/SessionLog 即 CLI 与测试承载的同一实现(非转发壳)。
|
|
8
|
+
*
|
|
9
|
+
* 加法登记:1.11.0 —— AuthzService/checkCapability 出面(平台确认桥幂等决策 + 会话策略直判承载;F4 US-3 语义同源)。
|
|
8
10
|
*/
|
|
9
11
|
export { SessionLog, type SessionEvent, type EventKind, type Actor } from './session/log.ts';
|
|
10
12
|
export { SessionFileStore } from './session/store.ts';
|
|
@@ -17,5 +19,6 @@ export { mountPlugin } from './load/loader.ts';
|
|
|
17
19
|
export { Context } from './kernel/context.ts';
|
|
18
20
|
export { loadCarConfig, mergeLlmConfig, mergeSignatureGate, warmCarConfig, type CarConfig, type LlmConfig } from './load/config.ts';
|
|
19
21
|
export { probeCapabilities, SandboxExecutor } from './sandbox/sandbox.ts';
|
|
22
|
+
export { AuthzService, checkCapability, type Decision, type AuthzRequest, type AuthzDecision, type DeclaredCapabilities } from './authz/authz.ts';
|
|
20
23
|
export { createTelemetryFacade, telemetryConfigFromEnv } from './runtime-core/telemetry.ts';
|
|
21
24
|
export { TurnTracer, type TurnSpanHandle } from './runtime-core/trace.ts';
|
package/dist/seam.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* - 仅暴露进程内挂接所需的最窄面(装配/会话/turn/遥测),禁止透出内核内部模块路径语义;
|
|
6
6
|
* - 破坏性变更须内核 major;加法演进走 minor 并在本文件头登记;
|
|
7
7
|
* - 语义不变:runTurn/chatStep/SessionLog 即 CLI 与测试承载的同一实现(非转发壳)。
|
|
8
|
+
*
|
|
9
|
+
* 加法登记:1.11.0 —— AuthzService/checkCapability 出面(平台确认桥幂等决策 + 会话策略直判承载;F4 US-3 语义同源)。
|
|
8
10
|
*/
|
|
9
11
|
export { SessionLog } from './session/log.js';
|
|
10
12
|
export { SessionFileStore } from './session/store.js';
|
|
@@ -16,5 +18,6 @@ export { mountPlugin } from './load/loader.js';
|
|
|
16
18
|
export { Context } from './kernel/context.js';
|
|
17
19
|
export { loadCarConfig, mergeLlmConfig, mergeSignatureGate, warmCarConfig } from './load/config.js';
|
|
18
20
|
export { probeCapabilities, SandboxExecutor } from './sandbox/sandbox.js';
|
|
21
|
+
export { AuthzService, checkCapability } from './authz/authz.js';
|
|
19
22
|
export { createTelemetryFacade, telemetryConfigFromEnv } from './runtime-core/telemetry.js';
|
|
20
23
|
export { TurnTracer } from './runtime-core/trace.js';
|
package/dist/session/log.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system';
|
|
1
|
+
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage';
|
|
2
2
|
export type Actor = 'user' | 'model' | 'plugin' | 'runtime';
|
|
3
3
|
export interface SessionEvent {
|
|
4
4
|
seq: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lqc123qwe/car-runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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/authz/authz.ts
CHANGED
|
@@ -61,6 +61,18 @@ export class AuthzService {
|
|
|
61
61
|
return this.#commit(decision)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** 会话策略直判(「会话内始终允许」粒度):decidedBy=policy 幂等落链,不入人审通道(审批回调 scope=session 写入 grants 后走此路) */
|
|
65
|
+
decideBySessionPolicy(req: AuthzRequest): AuthzDecision {
|
|
66
|
+
return this.#commit({
|
|
67
|
+
authorizationId: req.authorizationId,
|
|
68
|
+
decision: 'APPROVED',
|
|
69
|
+
decidedBy: 'policy',
|
|
70
|
+
reason: `session policy grant (always-allow): ${req.resource}`,
|
|
71
|
+
degraded: this.#degraded,
|
|
72
|
+
ts: Date.now(),
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
/** 人审(confirm 模式):120s 超时默认拒绝(timeout-default-deny) */
|
|
65
77
|
async decideByUser(req: AuthzRequest, approve: (req: AuthzRequest) => Promise<boolean>): Promise<AuthzDecision> {
|
|
66
78
|
const cached = this.#decisions.get(req.authorizationId)
|
package/src/loop/stop.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* turn 内任一 step 撞限 → turn/end 保留 max-tokens,不被后续成功 step 冲销
|
|
11
11
|
* - 工具收口:concludesTurn=OR(任一 true 即收口);terminate=AND(整批 finalized 全 true 才中断)
|
|
12
12
|
* - 取消:未派发调用补记成对事件(toolCall + 合成错误 toolResult),日志无缺口
|
|
13
|
+
* - 1.11-S2(D-33 / 计量设计 §3.3):turnEnd meta.usage = 本 turn usage 事件四分量求和
|
|
14
|
+
* (聚合在内核消费侧单点 closeTurn——适配器无状态 ACL;无 usage 事件 → meta 不带 usage 键,
|
|
15
|
+
* 缺省=未采集向后兼容;撞限/异常 turn 照常聚合,截断前已完成请求的 usage 事实不吞)
|
|
13
16
|
*/
|
|
14
17
|
import type { SessionLog } from '../session/log.ts'
|
|
15
18
|
import { CarM8Error } from '../runtime-core/errors.ts'
|
|
@@ -58,6 +61,29 @@ export class TurnAborted extends Error {
|
|
|
58
61
|
constructor() { super('CAR-ABORTED: turn cancelled by user') }
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 1.11-S2(D-33 / 计量设计 §3.3):turn 级 usage 聚合——扫描本 turn 的 'usage' 事件(事实本体
|
|
66
|
+
* 在 JSONL,聚合由事实行求和而非运行时状态;字段非数值容错跳过不吞计数)。无 usage 事件
|
|
67
|
+
* 返回 undefined → turnEnd meta 不带 usage 键(缺省=未采集,旧格式向后兼容)。
|
|
68
|
+
*/
|
|
69
|
+
function turnUsageFrom(log: SessionLog, turnId: string): { input: number; output: number; cacheRead: number; cacheCreation: number; requests: number } | undefined {
|
|
70
|
+
let input = 0
|
|
71
|
+
let output = 0
|
|
72
|
+
let cacheRead = 0
|
|
73
|
+
let cacheCreation = 0
|
|
74
|
+
let requests = 0
|
|
75
|
+
for (const e of log.events) {
|
|
76
|
+
if (e.kind !== 'usage' || e.turnId !== turnId) continue
|
|
77
|
+
const p = (e.payload ?? {}) as Record<string, unknown>
|
|
78
|
+
if (typeof p.input === 'number') input += p.input
|
|
79
|
+
if (typeof p.output === 'number') output += p.output
|
|
80
|
+
if (typeof p.cacheRead === 'number') cacheRead += p.cacheRead
|
|
81
|
+
if (typeof p.cacheCreation === 'number') cacheCreation += p.cacheCreation
|
|
82
|
+
requests++
|
|
83
|
+
}
|
|
84
|
+
return requests ? { input, output, cacheRead, cacheCreation, requests } : undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
export async function runTurn(opts: {
|
|
62
88
|
log: SessionLog
|
|
63
89
|
turnId: string
|
|
@@ -72,6 +98,13 @@ export async function runTurn(opts: {
|
|
|
72
98
|
let sawMaxTokens = false
|
|
73
99
|
let steps = 0
|
|
74
100
|
|
|
101
|
+
// 1.11-S2(D-33):turnEnd 落链单点包装——聚合本 turn usage 事件(无 usage 不带键,
|
|
102
|
+
// 缺省=未采集向后兼容);reason 保持 meta 首键,usage 恒为加法尾键
|
|
103
|
+
const closeTurn = (meta: Record<string, unknown>) => {
|
|
104
|
+
const usage = turnUsageFrom(log, turnId)
|
|
105
|
+
return log.append('runtime', 'turnEnd', turnId, null, { ...meta, ...(usage ? { usage } : {}) })
|
|
106
|
+
}
|
|
107
|
+
|
|
75
108
|
const executeBatch = async (calls: ToolCall[]): Promise<{ concludesTurn: boolean; concludesAll: boolean; terminateVotes: number; terminateAll: boolean; finalized: number; denies: number }> => {
|
|
76
109
|
let concludesTurn = false
|
|
77
110
|
let concludesVotes = 0
|
|
@@ -122,7 +155,7 @@ export async function runTurn(opts: {
|
|
|
122
155
|
try {
|
|
123
156
|
while (steps < maxSteps) {
|
|
124
157
|
if (signal?.aborted) {
|
|
125
|
-
const seq =
|
|
158
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
126
159
|
trail.push({ seq, reason: 'aborted' })
|
|
127
160
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
128
161
|
}
|
|
@@ -131,7 +164,7 @@ export async function runTurn(opts: {
|
|
|
131
164
|
const step = await model()
|
|
132
165
|
|
|
133
166
|
if (step.stopReason === 'error') {
|
|
134
|
-
const seq =
|
|
167
|
+
const seq = closeTurn({ reason: 'error' }).seq
|
|
135
168
|
trail.push({ seq, reason: 'error' })
|
|
136
169
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
137
170
|
}
|
|
@@ -151,7 +184,7 @@ export async function runTurn(opts: {
|
|
|
151
184
|
continue
|
|
152
185
|
}
|
|
153
186
|
// 默认收口:截断响应不解析、不执行任何工具调用
|
|
154
|
-
const seq =
|
|
187
|
+
const seq = closeTurn({ reason: 'max-tokens' }).seq
|
|
155
188
|
trail.push({ seq, reason: 'max-tokens' })
|
|
156
189
|
return { reason: 'max-tokens', steps, endReasonTrail: trail }
|
|
157
190
|
}
|
|
@@ -160,21 +193,21 @@ export async function runTurn(opts: {
|
|
|
160
193
|
const batch = await executeBatch(step.toolCalls)
|
|
161
194
|
// F13:授权拒绝即收口(blockOnDeny 产出点——blocked 仅新增产出点,枚举不增)
|
|
162
195
|
if (preset.blockOnDeny && batch.denies > 0) {
|
|
163
|
-
const seq =
|
|
196
|
+
const seq = closeTurn({ reason: 'blocked', denies: batch.denies }).seq
|
|
164
197
|
trail.push({ seq, reason: 'blocked' })
|
|
165
198
|
return { reason: 'blocked', steps, endReasonTrail: trail }
|
|
166
199
|
}
|
|
167
200
|
const agg = preset.aggregate ?? {}
|
|
168
201
|
const abortedNow = (agg.terminate ?? 'all') === 'any' ? batch.terminateVotes > 0 : batch.terminateAll
|
|
169
202
|
if (abortedNow) { // terminate 收口:默认 AND(整批),可配置 any
|
|
170
|
-
const seq =
|
|
203
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
171
204
|
trail.push({ seq, reason: 'aborted' })
|
|
172
205
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
173
206
|
}
|
|
174
207
|
const concludedNow = (agg.concludesTurn ?? 'any') === 'any' ? batch.concludesTurn : batch.concludesAll && batch.finalized > 0
|
|
175
208
|
if (concludedNow) { // concludesTurn 收口:默认 OR,可配置 all
|
|
176
209
|
const reason: TurnEndReason = sawMaxTokens ? 'max-tokens' : 'completed'
|
|
177
|
-
const seq =
|
|
210
|
+
const seq = closeTurn({ reason }).seq
|
|
178
211
|
trail.push({ seq, reason })
|
|
179
212
|
return { reason, steps, endReasonTrail: trail }
|
|
180
213
|
}
|
|
@@ -184,16 +217,16 @@ export async function runTurn(opts: {
|
|
|
184
217
|
|
|
185
218
|
// stopReason === 'stop':模型自然完成
|
|
186
219
|
const reason: TurnEndReason = sawMaxTokens ? 'max-tokens' : 'completed'
|
|
187
|
-
const seq =
|
|
220
|
+
const seq = closeTurn({ reason }).seq
|
|
188
221
|
trail.push({ seq, reason })
|
|
189
222
|
return { reason, steps, endReasonTrail: trail }
|
|
190
223
|
}
|
|
191
|
-
const seq =
|
|
224
|
+
const seq = closeTurn({ reason: 'error', detail: 'max-steps' }).seq
|
|
192
225
|
trail.push({ seq, reason: 'error' })
|
|
193
226
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
194
227
|
} catch (e) {
|
|
195
228
|
if (e instanceof TurnAborted) {
|
|
196
|
-
const seq =
|
|
229
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
197
230
|
trail.push({ seq, reason: 'aborted' })
|
|
198
231
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
199
232
|
}
|
|
@@ -201,7 +234,7 @@ export async function runTurn(opts: {
|
|
|
201
234
|
// car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变)
|
|
202
235
|
const m8 = e instanceof CarM8Error
|
|
203
236
|
const detail = m8 ? `${e.slug}: ${e.message}` : String(e)
|
|
204
|
-
const seq =
|
|
237
|
+
const seq = closeTurn({
|
|
205
238
|
reason: 'error', detail,
|
|
206
239
|
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
|
|
207
240
|
}).seq
|
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
* (半截 JSON 不解析——ADR-001 同纪律,length 时只交 truncatedTools);
|
|
13
13
|
* 5. assistant 文本事件落 M7(脱敏后 + secretsRedacted 计数留痕 meta);
|
|
14
14
|
* 6. finishReason 不可变透传为 ModelStep.stopReason(error/aborted → 'error',BD-04 收口
|
|
15
|
-
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch
|
|
15
|
+
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch 落(本层不重复);
|
|
16
|
+
* 7. 1.11-S2(D-33 / 计量设计 §3.3):流尾 usage chunk → 'runtime'/'usage' 请求级事实落链
|
|
17
|
+
* (缺失 = provider 未回/流中断/畸形,禁补造;actor='runtime'、model_visible=0 不进模型
|
|
18
|
+
* 消息流)——turn 级聚合在 stop.ts 收口单点,本层不做跨请求状态。
|
|
16
19
|
*/
|
|
17
20
|
import type { SessionLog } from '../session/log.ts'
|
|
18
21
|
import { TurnAborted, type ModelStep, type ToolCall } from '../loop/stop.ts'
|
|
19
22
|
import type { RuntimeCore } from './llm.ts'
|
|
20
|
-
import type { ToolDefinition } from './types.ts'
|
|
23
|
+
import type { LlmUsage, ToolDefinition } from './types.ts'
|
|
21
24
|
import { StreamRedactor } from './redaction.ts'
|
|
22
25
|
import { scanSecrets } from '../security/secrets.ts'
|
|
23
26
|
import { providerUnreachable } from './errors.ts'
|
|
@@ -67,6 +70,7 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
67
70
|
const calls = new Map<number, { id?: string; name?: string; argsBuf: string }>()
|
|
68
71
|
let stopReason: ModelStep['stopReason'] | undefined
|
|
69
72
|
let errorDetail: string | undefined
|
|
73
|
+
let usage: LlmUsage | undefined
|
|
70
74
|
|
|
71
75
|
const stream = core.chat({
|
|
72
76
|
model,
|
|
@@ -90,6 +94,7 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
90
94
|
calls.set(idx, cur)
|
|
91
95
|
}
|
|
92
96
|
if (chunk.error) errorDetail = chunk.error.message
|
|
97
|
+
if (chunk.usage) usage = chunk.usage // D-33:附着于最后一个 chunk——后到覆盖
|
|
93
98
|
if (chunk.finishReason !== undefined) stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason
|
|
94
99
|
}
|
|
95
100
|
// 1.4-S4(D-19):finish 检查前先查取消(适配器取消路径静默收口无 finishReason——
|
|
@@ -101,6 +106,21 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
101
106
|
}
|
|
102
107
|
text += redactor.flush()
|
|
103
108
|
|
|
109
|
+
// 1.11-S2(D-33 / 计量设计 §3.3):请求级 usage 事实落链(拿到 usage 的最后一个 chunk 即请求
|
|
110
|
+
// 收尾;撞限/异常 turn 照常落——截断前已完成请求的 usage 事实不吞)。缺失 = provider 未回,
|
|
111
|
+
// 禁补造;actor='runtime'、不进模型消息流(deriveMessages 不投影 usage,§2.5 红线)
|
|
112
|
+
if (usage) {
|
|
113
|
+
log.append('runtime', 'usage', turnId, {
|
|
114
|
+
model: usage.model,
|
|
115
|
+
adapterId: usage.adapterId,
|
|
116
|
+
input: usage.inputTokens,
|
|
117
|
+
output: usage.outputTokens,
|
|
118
|
+
cacheRead: usage.cacheReadTokens,
|
|
119
|
+
cacheCreation: usage.cacheCreationTokens,
|
|
120
|
+
providerRaw: usage.providerRaw,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
// 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
|
|
105
125
|
// flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
|
|
106
126
|
let secretsRedacted = 0
|
package/src/runtime-core/llm.ts
CHANGED
|
@@ -20,10 +20,15 @@
|
|
|
20
20
|
* 0xC0000409)——car run 真路径 ≥3 次模型请求 + PTC worker/MCP 子进程并存即确定性复现
|
|
21
21
|
* (1.4 彼时仅修遥测出站,LLM 出站同为 undici 默认——潜伏至 1.8 E2E 三请求形态暴露);
|
|
22
22
|
* 流式契约 body = AsyncIterable<Uint8Array> 不变;测试注入缝 fetchImpl 保留。
|
|
23
|
+
* - 1.11-S1(D-33 / 计量设计 §3.2):usage 采集——openai 请求体加 stream_options.include_usage
|
|
24
|
+
* (默认开启,第三方网关不兼容时回退 = baseUrl 白名单,登记可回退项;缺失走缺失口径禁估算),
|
|
25
|
+
* 解析「先摘 evt.usage 再判 choice」(空 choices 尾包不再被吞);anthropic message_start(input 侧)
|
|
26
|
+
* + message_delta(output 侧)两包合并(message_delta 缺席 = output 不完整,整条不落);
|
|
27
|
+
* 归一口径 §1.1(openai 减法 / anthropic 透传 + cache_creation),畸形/缺失整条不落(禁补造)。
|
|
23
28
|
*/
|
|
24
29
|
import { CarM8Error, providerUnreachable } from './errors.ts'
|
|
25
30
|
import { CredentialService } from './credentials.ts'
|
|
26
|
-
import type { Disposable, LlmAdapter, LlmChunk, LlmRequest, ToolCallDelta } from './types.ts'
|
|
31
|
+
import type { Disposable, LlmAdapter, LlmChunk, LlmRequest, LlmUsage, ToolCallDelta } from './types.ts'
|
|
27
32
|
import type { PluginContext } from '../kernel/context.ts'
|
|
28
33
|
import { request as httpRequest } from 'node:http'
|
|
29
34
|
import { request as httpsRequest } from 'node:https'
|
|
@@ -279,8 +284,30 @@ const FINISH_MAP: Record<string, LlmChunk['finishReason']> = {
|
|
|
279
284
|
|
|
280
285
|
class Retryable extends Error {}
|
|
281
286
|
|
|
282
|
-
/**
|
|
283
|
-
|
|
287
|
+
/**
|
|
288
|
+
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
289
|
+
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
290
|
+
* 登记不估算)。usage 对象缺席/畸形(两侧主字段任一非数值)= 整条不落(缺失口径,禁补造)。
|
|
291
|
+
*/
|
|
292
|
+
function normalizeOpenAIUsage(u: unknown, model: string, adapterId: string): LlmUsage | undefined {
|
|
293
|
+
if (!u || typeof u !== 'object') return undefined
|
|
294
|
+
const r = u as Record<string, unknown>
|
|
295
|
+
if (typeof r.prompt_tokens !== 'number' || typeof r.completion_tokens !== 'number') return undefined
|
|
296
|
+
const details = (r.prompt_tokens_details ?? null) as Record<string, unknown> | null
|
|
297
|
+
const cached = details && typeof details.cached_tokens === 'number' ? details.cached_tokens : 0
|
|
298
|
+
return {
|
|
299
|
+
inputTokens: r.prompt_tokens - cached,
|
|
300
|
+
outputTokens: r.completion_tokens,
|
|
301
|
+
cacheReadTokens: cached,
|
|
302
|
+
cacheCreationTokens: 0,
|
|
303
|
+
model,
|
|
304
|
+
adapterId,
|
|
305
|
+
providerRaw: u,
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** 明文 SSE → LlmChunk 流解析(增量容错:半行驻留到下一 chunk;1.11-S1:model/adapterId 供 usage 归一) */
|
|
310
|
+
async function* parseSseStream(body: AsyncIterable<Uint8Array>, model: string, adapterId: string): AsyncIterable<LlmChunk> {
|
|
284
311
|
const decoder = new TextDecoder()
|
|
285
312
|
let buf = ''
|
|
286
313
|
for await (const bytes of body) {
|
|
@@ -295,8 +322,15 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>): AsyncIterable<L
|
|
|
295
322
|
if (!data) continue
|
|
296
323
|
let evt: any
|
|
297
324
|
try { evt = JSON.parse(data) } catch { continue } // 心跳/注释行容错(显式跳过非 JSON data)
|
|
325
|
+
// 1.11-S1(D-33 / 计量设计 §3.2 必核行):先摘 usage 再判 choice——stream_options.include_usage
|
|
326
|
+
// 的尾包 choices 为空,下方 `if (!choice) continue` 原样会把它吞掉
|
|
327
|
+
const usage = evt.usage !== undefined ? normalizeOpenAIUsage(evt.usage, model, adapterId) : undefined
|
|
298
328
|
const choice = evt.choices?.[0]
|
|
299
|
-
if (!choice)
|
|
329
|
+
if (!choice) {
|
|
330
|
+
// 独立 usage 尾包(官方形态:finish_reason chunk 之后、[DONE] 之前)——请求最后一个 chunk
|
|
331
|
+
if (usage) yield { usage }
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
300
334
|
const delta = choice.delta ?? {}
|
|
301
335
|
const raw = choice.finish_reason
|
|
302
336
|
const finish: LlmChunk['finishReason'] | undefined = raw == null ? undefined : (() => {
|
|
@@ -315,26 +349,34 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>): AsyncIterable<L
|
|
|
315
349
|
: []
|
|
316
350
|
const text = typeof delta.content === 'string' && delta.content.length ? delta.content : undefined
|
|
317
351
|
// LlmChunk 单 toolCallDelta 口径:同事件多条 tool_calls 顺序展开为多 chunk
|
|
318
|
-
if (!text && !toolCallDeltas.length && finish === undefined)
|
|
352
|
+
if (!text && !toolCallDeltas.length && finish === undefined) {
|
|
353
|
+
// 空事件但携 usage(部分 compat 网关形态):usage 不随空事件丢失
|
|
354
|
+
if (usage) yield { usage }
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
const chunks: LlmChunk[] = []
|
|
319
358
|
if (toolCallDeltas.length === 0) {
|
|
320
|
-
|
|
359
|
+
chunks.push({
|
|
321
360
|
...(text !== undefined ? { delta: text } : {}),
|
|
322
361
|
...(finish !== undefined
|
|
323
362
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
324
363
|
: {}),
|
|
325
|
-
}
|
|
364
|
+
})
|
|
326
365
|
} else {
|
|
327
366
|
for (let i = 0; i < toolCallDeltas.length; i++) {
|
|
328
367
|
const last = i === toolCallDeltas.length - 1
|
|
329
|
-
|
|
368
|
+
chunks.push({
|
|
330
369
|
...(i === 0 && text !== undefined ? { delta: text } : {}),
|
|
331
370
|
toolCallDelta: toolCallDeltas[i],
|
|
332
371
|
...(last && finish !== undefined
|
|
333
372
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
334
373
|
: {}),
|
|
335
|
-
}
|
|
374
|
+
})
|
|
336
375
|
}
|
|
337
376
|
}
|
|
377
|
+
// 1.11-S1(D-33):usage 与事件内容同 chunk 到达(网关共存形态)→ 附着于本事件最后一个 chunk
|
|
378
|
+
if (usage) chunks[chunks.length - 1]!.usage = usage
|
|
379
|
+
for (const c of chunks) yield c
|
|
338
380
|
}
|
|
339
381
|
}
|
|
340
382
|
}
|
|
@@ -364,6 +406,9 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
364
406
|
body: JSON.stringify({
|
|
365
407
|
model: req.model,
|
|
366
408
|
stream: true,
|
|
409
|
+
// 1.11-S1(D-33 / 计量设计 §9-2 推荐A):usage 采集默认开启;第三方网关不兼容时的
|
|
410
|
+
// 回退方案(baseUrl 白名单)登记可回退项——不支持网关走缺失口径(禁估算补造)
|
|
411
|
+
stream_options: { include_usage: true },
|
|
367
412
|
...(req.maxTokens != null ? { max_tokens: req.maxTokens } : {}),
|
|
368
413
|
messages: req.messages.map(m => {
|
|
369
414
|
if (m.role === 'toolResult') {
|
|
@@ -402,7 +447,7 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
402
447
|
fetchImpl,
|
|
403
448
|
signal: req.signal,
|
|
404
449
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
405
|
-
}, parseSseStream)
|
|
450
|
+
}, (body) => parseSseStream(body, req.model, id))
|
|
406
451
|
},
|
|
407
452
|
}
|
|
408
453
|
}
|
|
@@ -436,6 +481,31 @@ const ANTHROPIC_STOP_REASON_MAP: Record<string, LlmChunk['finishReason']> = {
|
|
|
436
481
|
refusal: 'aborted',
|
|
437
482
|
}
|
|
438
483
|
|
|
484
|
+
/**
|
|
485
|
+
* 1.11-S1(D-33 / 计量设计 §3.2):anthropic usage 两包合并——message_start(input 侧:
|
|
486
|
+
* input_tokens 天然不含 cache + cache_read/cache_creation)+ message_delta(output 侧:
|
|
487
|
+
* output_tokens)。任一包缺席或主字段非数值 = 侧不完整,整条不落(缺失口径,禁补造半条事实);
|
|
488
|
+
* providerRaw 保留两原始片段供审计对拍。
|
|
489
|
+
*/
|
|
490
|
+
function mergeAnthropicUsage(
|
|
491
|
+
start: Record<string, unknown> | null,
|
|
492
|
+
delta: Record<string, unknown> | null,
|
|
493
|
+
model: string,
|
|
494
|
+
adapterId: string,
|
|
495
|
+
): LlmUsage | undefined {
|
|
496
|
+
if (!start || !delta) return undefined
|
|
497
|
+
if (typeof start.input_tokens !== 'number' || typeof delta.output_tokens !== 'number') return undefined
|
|
498
|
+
return {
|
|
499
|
+
inputTokens: start.input_tokens,
|
|
500
|
+
outputTokens: delta.output_tokens,
|
|
501
|
+
cacheReadTokens: typeof start.cache_read_input_tokens === 'number' ? start.cache_read_input_tokens : 0,
|
|
502
|
+
cacheCreationTokens: typeof start.cache_creation_input_tokens === 'number' ? start.cache_creation_input_tokens : 0,
|
|
503
|
+
model,
|
|
504
|
+
adapterId,
|
|
505
|
+
providerRaw: { messageStart: start, messageDelta: delta },
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
439
509
|
/**
|
|
440
510
|
* Anthropic Messages API 请求体(D-20):system 消息提升为顶层 `system`(多条 \n\n 连接);
|
|
441
511
|
* toolResult 收敛进 user 消息 content(连续多条合并为单 user 多 tool_result 块——Anthropic
|
|
@@ -499,10 +569,13 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
|
|
|
499
569
|
* Anthropic SSE → LlmChunk 流解析(data-only:data JSON 自带 `type` 字段,`event:` 行不依赖)。
|
|
500
570
|
* content_block_start(tool_use) → toolCallDelta{id,name};input_json_delta → toolCallDelta{argumentsDelta};
|
|
501
571
|
* message_delta.stop_reason → finishReason(终态信号,单次);ping/message_stop/未知事件容错跳过。
|
|
572
|
+
* 1.11-S1(D-33):message_start.usage 采集 input 侧(原解析整体跳过该事件)+ message_delta.usage
|
|
573
|
+
* 合并 output 侧附着于终态 chunk;流中断 delta 未到 = output 侧不完整,整条不落(R-2)。
|
|
502
574
|
*/
|
|
503
|
-
async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array
|
|
575
|
+
async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model: string, adapterId: string): AsyncIterable<LlmChunk> {
|
|
504
576
|
const decoder = new TextDecoder()
|
|
505
577
|
let buf = ''
|
|
578
|
+
let startUsage: Record<string, unknown> | null = null
|
|
506
579
|
for await (const bytes of body) {
|
|
507
580
|
buf += decoder.decode(bytes, { stream: true })
|
|
508
581
|
let nl: number
|
|
@@ -528,13 +601,23 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>): AsyncI
|
|
|
528
601
|
yield { toolCallDelta: { index: evt.index, argumentsDelta: d.partial_json } }
|
|
529
602
|
}
|
|
530
603
|
// thinking_delta / citations_delta 等非 CAR 消费面:容错跳过
|
|
604
|
+
} else if (type === 'message_start') {
|
|
605
|
+
// 1.11-S1(D-33):input 侧采集(usage 嵌套在 message 下)
|
|
606
|
+
const u = (evt.message as Record<string, unknown> | undefined)?.usage
|
|
607
|
+
if (u && typeof u === 'object') startUsage = u as Record<string, unknown>
|
|
531
608
|
} else if (type === 'message_delta') {
|
|
532
609
|
const raw = evt.delta?.stop_reason
|
|
610
|
+
// 1.11-S1(D-33):output 侧(delta.usage.output_tokens)与 input 侧合并——两包齐才落
|
|
611
|
+
const usage = mergeAnthropicUsage(startUsage, (evt.usage ?? null) as Record<string, unknown> | null, model, adapterId)
|
|
533
612
|
if (raw != null) {
|
|
534
613
|
const finish = ANTHROPIC_STOP_REASON_MAP[String(raw)]
|
|
535
|
-
|
|
614
|
+
const chunk: LlmChunk = finish
|
|
536
615
|
? { finishReason: finish }
|
|
537
616
|
: { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"` } }
|
|
617
|
+
yield usage ? { ...chunk, usage } : chunk
|
|
618
|
+
} else if (usage) {
|
|
619
|
+
// delta 未携 stop_reason 但携 usage(网关形态):usage 不随缺失终态信号丢失
|
|
620
|
+
yield { usage }
|
|
538
621
|
}
|
|
539
622
|
} else if (type === 'error') {
|
|
540
623
|
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta 由守卫拦冲突)
|
|
@@ -575,7 +658,7 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
|
|
|
575
658
|
fetchImpl,
|
|
576
659
|
signal: req.signal,
|
|
577
660
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
578
|
-
}, parseAnthropicSseStream)
|
|
661
|
+
}, (body) => parseAnthropicSseStream(body, req.model, id))
|
|
579
662
|
},
|
|
580
663
|
}
|
|
581
664
|
}
|
|
@@ -36,14 +36,35 @@ export interface ToolCallDelta {
|
|
|
36
36
|
argumentsDelta?: string
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 1.11-S1(D-33 / 计量设计 §3.1):token 四分量归一化用量——跨 provider 可加总的库内口径
|
|
41
|
+
* (§1.1:openai prompt_tokens 含 cached 必须减法归一;anthropic input_tokens 天然不含 cache)。
|
|
42
|
+
* provider 原始 usage 整体存 providerRaw 供审计对拍(对拍恒以 provider_raw 为准)。
|
|
43
|
+
*/
|
|
44
|
+
export interface LlmUsage {
|
|
45
|
+
/** 非缓存输入 token(归一化后) */
|
|
46
|
+
inputTokens: number
|
|
47
|
+
outputTokens: number
|
|
48
|
+
cacheReadTokens: number
|
|
49
|
+
/** openai 系无对应字段,恒 0(登记不估算) */
|
|
50
|
+
cacheCreationTokens: number
|
|
51
|
+
/** 实际请求模型(req.model) */
|
|
52
|
+
model: string
|
|
53
|
+
adapterId: string
|
|
54
|
+
/** provider 原始 usage 对象(anthropic 为 messageStart/messageDelta 两原始片段) */
|
|
55
|
+
providerRaw: unknown
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
/** 流式块(finishReason 一经产生不可被任何中间层改写) */
|
|
40
59
|
export interface LlmChunk {
|
|
41
60
|
delta?: string
|
|
42
61
|
toolCallDelta?: ToolCallDelta
|
|
43
62
|
finishReason?: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'
|
|
44
|
-
// —— 规格外加法扩展(登记:M8 收口文档 §4)——
|
|
63
|
+
// —— 规格外加法扩展(登记:M8 收口文档 §4 / 计量设计 §3.1,1.11.0)——
|
|
45
64
|
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta,不进模型消息流) */
|
|
46
65
|
error?: { code: string; message: string }
|
|
66
|
+
/** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
|
|
67
|
+
usage?: LlmUsage
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
/** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
|
package/src/seam.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* - 仅暴露进程内挂接所需的最窄面(装配/会话/turn/遥测),禁止透出内核内部模块路径语义;
|
|
6
6
|
* - 破坏性变更须内核 major;加法演进走 minor 并在本文件头登记;
|
|
7
7
|
* - 语义不变:runTurn/chatStep/SessionLog 即 CLI 与测试承载的同一实现(非转发壳)。
|
|
8
|
+
*
|
|
9
|
+
* 加法登记:1.11.0 —— AuthzService/checkCapability 出面(平台确认桥幂等决策 + 会话策略直判承载;F4 US-3 语义同源)。
|
|
8
10
|
*/
|
|
9
11
|
export { SessionLog, type SessionEvent, type EventKind, type Actor } from './session/log.ts'
|
|
10
12
|
export { SessionFileStore } from './session/store.ts'
|
|
@@ -17,5 +19,6 @@ export { mountPlugin } from './load/loader.ts'
|
|
|
17
19
|
export { Context } from './kernel/context.ts'
|
|
18
20
|
export { loadCarConfig, mergeLlmConfig, mergeSignatureGate, warmCarConfig, type CarConfig, type LlmConfig } from './load/config.ts'
|
|
19
21
|
export { probeCapabilities, SandboxExecutor } from './sandbox/sandbox.ts'
|
|
22
|
+
export { AuthzService, checkCapability, type Decision, type AuthzRequest, type AuthzDecision, type DeclaredCapabilities } from './authz/authz.ts'
|
|
20
23
|
export { createTelemetryFacade, telemetryConfigFromEnv } from './runtime-core/telemetry.ts'
|
|
21
24
|
export { TurnTracer, type TurnSpanHandle } from './runtime-core/trace.ts'
|
package/src/session/log.ts
CHANGED
|
@@ -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' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 =
|
|
14
|
+
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' // 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 不变
|
|
15
15
|
export type Actor = 'user' | 'model' | 'plugin' | 'runtime'
|
|
16
16
|
export interface SessionEvent {
|
|
17
17
|
seq: number
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"base64Signature":"MEUCIQC9Eb4tjZzDe1mzgXUycmzYBYKgL6Jy5Hk2QNYQDXwEAQIgYQ8bVRThONnvlokYD1GdQgBQ3KxeKvH2RtK4/0VZSz4=","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhFakNDQnBtZ0F3SUJBZ0lVVkJKazAvbWwrckMvZ01PcVlOVk1vSVpTbEV3d0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpZd09USTVNRGN6TWpNeFdoY05Nall3T1RJNU1EYzBNak14V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVLS2lkTGZWTUR6RGZJT1hocTRRanhkbjZUdUJhK0xBSXRMQVcKZFVMWkJoRnFLTFgzSGg0MlJCdWorOWVCQmZ3cDhtbjJDRmJvbWcya1NJN1lGUkg3V2FPQ0JiZ3dnZ1cwTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVFMlA0CkNEd3Rta0ZPVTJwNEhoNk5iU1NRM3Bvd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d1lRWURWUjBSQVFIL0JGY3dWWVpUYUhSMGNITTZMeTluYVhSb2RXSXVZMjl0TDJ4eFl6WXlOQzlqWVhJdApjblZ1ZEdsdFpTOHVaMmwwYUhWaUwzZHZjbXRtYkc5M2N5OXlaV3hsWVhObExubHRiRUJ5WldaekwyaGxZV1J6CkwyMWhhVzR3T1FZS0t3WUJCQUdEdnpBQkFRUXJhSFIwY0hNNkx5OTBiMnRsYmk1aFkzUnBiMjV6TG1kcGRHaDEKWW5WelpYSmpiMjUwWlc1MExtTnZiVEFmQmdvckJnRUVBWU8vTUFFQ0JCRjNiM0pyWm14dmQxOWthWE53WVhSagphREEyQmdvckJnRUVBWU8vTUFFREJDaGtaRGswWlRCaFpETmpNbUZqWmpFM1kyRTNOelpqTWpreVpEUTFZV0UzCk9EazFNV1ZtWTJWbE1CVUdDaXNHQVFRQmc3OHdBUVFFQjNKbGJHVmhjMlV3SUFZS0t3WUJCQUdEdnpBQkJRUVMKYkhGak5qSTBMMk5oY2kxeWRXNTBhVzFsTUIwR0Npc0dBUVFCZzc4d0FRWUVEM0psWm5NdmFHVmhaSE12YldGcApiakE3QmdvckJnRUVBWU8vTUFFSUJDME1LMmgwZEhCek9pOHZkRzlyWlc0dVlXTjBhVzl1Y3k1bmFYUm9kV0oxCmMyVnlZMjl1ZEdWdWRDNWpiMjB3WXdZS0t3WUJCQUdEdnpBQkNRUlZERk5vZEhSd2N6b3ZMMmRwZEdoMVlpNWoKYjIwdmJIRmpOakkwTDJOaGNpMXlkVzUwYVcxbEx5NW5hWFJvZFdJdmQyOXlhMlpzYjNkekwzSmxiR1ZoYzJVdQplVzFzUUhKbFpuTXZhR1ZoWkhNdmJXRnBiakE0QmdvckJnRUVBWU8vTUFFS0JDb01LR1JrT1RSbE1HRmtNMk15CllXTm1NVGRqWVRjM05tTXlPVEprTkRWaFlUYzRPVFV4WldaalpXVXdIUVlLS3dZQkJBR0R2ekFCQ3dRUERBMW4KYVhSb2RXSXRhRzl6ZEdWa01EVUdDaXNHQVFRQmc3OHdBUXdFSnd3bGFIUjBjSE02THk5bmFYUm9kV0l1WTI5dApMMnh4WXpZeU5DOWpZWEl0Y25WdWRHbHRaVEE0QmdvckJnRUVBWU8vTUFFTkJDb01LR1JrT1RSbE1HRmtNMk15CllXTm1NVGRqWVRjM05tTXlPVEprTkRWaFlUYzRPVFV4WldaalpXVXdId1lLS3dZQkJBR0R2ekFCRGdRUkRBOXkKWldaekwyaGxZV1J6TDIxaGFXNHdHZ1lLS3dZQkJBR0R2ekFCRHdRTURBb3hNell4TVRreU56RTBNQ2tHQ2lzRwpBUVFCZzc4d0FSQUVHd3daYUhSMGNITTZMeTluYVhSb2RXSXVZMjl0TDJ4eFl6WXlOREFZQmdvckJnRUVBWU8vCk1BRVJCQW9NQ0RZNU1qTTBOVFkwTUdNR0Npc0dBUVFCZzc4d0FSSUVWUXhUYUhSMGNITTZMeTluYVhSb2RXSXUKWTI5dEwyeHhZell5TkM5allYSXRjblZ1ZEdsdFpTOHVaMmwwYUhWaUwzZHZjbXRtYkc5M2N5OXlaV3hsWVhObApMbmx0YkVCeVpXWnpMMmhsWVdSekwyMWhhVzR3T0FZS0t3WUJCQUdEdnpBQkV3UXFEQ2hrWkRrMFpUQmhaRE5qCk1tRmpaakUzWTJFM056WmpNamt5WkRRMVlXRTNPRGsxTVdWbVkyVmxNQ0VHQ2lzR0FRUUJnNzh3QVJRRUV3d1IKZDI5eWEyWnNiM2RmWkdsemNHRjBZMmd3V1FZS0t3WUJCQUdEdnpBQkZRUkxERWxvZEhSd2N6b3ZMMmRwZEdoMQpZaTVqYjIwdmJIRmpOakkwTDJOaGNpMXlkVzUwYVcxbEwyRmpkR2x2Ym5NdmNuVnVjeTh6TmpVek56SXlOalV5Ck5DOWhkSFJsYlhCMGN5OHhNQllHQ2lzR0FRUUJnNzh3QVJZRUNBd0djSFZpYkdsak1FOEdDaXNHQVFRQmc3OHcKQVJnRVFRdy9jbVZ3Ynpwc2NXTTJNalJBTmpreU16UTFOalF2WTJGeUxYSjFiblJwYldWQU1UTTJNVEU1TWpjeApORHB5WldZNmNtVm1jeTlvWldGa2N5OXRZV2x1TUlHSkJnb3JCZ0VFQWRaNUFnUUNCSHNFZVFCM0FIVUEzVDB3CmFzYkhFVEpqR1I0Y21XYzNBcUpLWHJqZVBLMy9oNHB5Z0M4cDdvNEFBQUdnN0JSYVZRQUFCQU1BUmpCRUFpQUgKRlJxT20zUlk4TWJ6RVhPS08vQjRNSW9PR1ZpSk5McW9UaE0rUERRd3hnSWdIczJLKzZKelJyT1h6OGwya3JJVgpXdzBKRktTcExXNEJUbDhvcHhrbTJSSXdDZ1lJS29aSXpqMEVBd01EWndBd1pBSXdJWXorK3FLMi9sZHJ4TmxuCnpvU2FlcTl6UWorOE1odnFOWXVXbVV4MVArN1VFc2pNcTh2cjlZZkg4Y09WNnR3ZEFqQUIyWExlZFlFNlRXdmYKbEI4Wk1xNVVodEQwR01nZXJLREhibGR6d0xFcHg2YnM3QUJTbEdXb1RyajdBbVJibzNVPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","rekorBundle":{"SignedEntryTimestamp":"MEYCIQCQSshImz2LhSdIM0EyENAxTY1e+WvxVUnnr33WAxBy8wIhAIS1JWDTUupB9GK+eAdIDUt9uJeziuzX8v1UjrNl+r8j","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI5ZTFkOWQxODY1ZjQ2OTA2NGZjZTcyNGQ2NDZiOGEzYzliODdjODk3ODY4YzU5YTM0ODgwMGMwYzY0Y2I5ZWY5In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUM5RWI0dGpaekRlMW16Z1hVeWNtellCWUtnTDZKeTVIazJRTllRRFh3RUFRSWdZUThiVlJUaE9ObnZsb2tZRDFHZFFnQlEzS3hlS3ZIMlJ0SzQvMFZaU3o0PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaEZha05EUW5CdFowRjNTVUpCWjBsVlZrSkthekF2Yld3cmNrTXZaMDFQY1ZsT1ZrMXZTVnBUYkVWM2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDlVU1RWTlJHTjZUV3BOZUZkb1kwNU5hbGwzVDFSSk5VMUVZekJOYWsxNFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZMUzJsa1RHWldUVVI2UkdaSlQxaG9jVFJSYW5oa2JqWlVkVUpoSzB4QlNYUk1RVmNLWkZWTVdrSm9SbkZMVEZnelNHZzBNbEpDZFdvck9XVkNRbVozY0RodGJqSkRSbUp2YldjeWExTkpOMWxHVWtnM1YyRlBRMEppWjNkbloxY3dUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZGTWxBMENrTkVkM1J0YTBaUFZUSndORWhvTms1aVUxTlJNM0J2ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDFsUldVUldVakJTUVZGSUwwSkdZM2RXV1ZwVVlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKNGVGbDZXWGxPUXpscVdWaEpkQXBqYmxaMVpFZHNkRnBUT0hWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU0yTjVPWGxhVjNoc1dWaE9iRXh1YkhSaVJVSjVXbGRhZWt3eWFHeFpWMUo2Q2t3eU1XaGhWelIzVDFGWlMwdDNXVUpDUVVkRWRucEJRa0ZSVVhKaFNGSXdZMGhOTmt4NU9UQmlNblJzWW1rMWFGa3pVbkJpTWpWNlRHMWtjR1JIYURFS1dXNVdlbHBZU21waU1qVXdXbGMxTUV4dFRuWmlWRUZtUW1kdmNrSm5SVVZCV1U4dlRVRkZRMEpDUmpOaU0wcHlXbTE0ZG1ReE9XdGhXRTUzV1ZoU2FncGhSRUV5UW1kdmNrSm5SVVZCV1U4dlRVRkZSRUpEYUd0YVJHc3dXbFJDYUZwRVRtcE5iVVpxV21wRk0xa3lSVE5PZWxwcVRXcHJlVnBFVVRGWlYwVXpDazlFYXpGTlYxWnRXVEpXYkUxQ1ZVZERhWE5IUVZGUlFtYzNPSGRCVVZGRlFqTktiR0pIVm1oak1sVjNTVUZaUzB0M1dVSkNRVWRFZG5wQlFrSlJVVk1LWWtoR2FrNXFTVEJNTWs1b1kya3hlV1JYTlRCaFZ6RnNUVUl3UjBOcGMwZEJVVkZDWnpjNGQwRlJXVVZFTTBwc1dtNU5kbUZIVm1oYVNFMTJZbGRHY0FwaWFrRTNRbWR2Y2tKblJVVkJXVTh2VFVGRlNVSkRNRTFMTW1nd1pFaENlazlwT0haa1J6bHlXbGMwZFZsWFRqQmhWemwxWTNrMWJtRllVbTlrVjBveENtTXlWbmxaTWpsMVpFZFdkV1JETldwaU1qQjNXWGRaUzB0M1dVSkNRVWRFZG5wQlFrTlJVbFpFUms1dlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5Xb0tZakl3ZG1KSVJtcE9ha2t3VERKT2FHTnBNWGxrVnpVd1lWY3hiRXg1Tlc1aFdGSnZaRmRKZG1ReU9YbGhNbHB6WWpOa2Vrd3pTbXhpUjFab1l6SlZkUXBsVnpGelVVaEtiRnB1VFhaaFIxWm9Xa2hOZG1KWFJuQmlha0UwUW1kdmNrSm5SVVZCV1U4dlRVRkZTMEpEYjAxTFIxSnJUMVJTYkUxSFJtdE5NazE1Q2xsWFRtMU5WR1JxV1ZSak0wNXRUWGxQVkVwclRrUldhRmxVWXpSUFZGVjRXbGRhYWxwWFZYZElVVmxMUzNkWlFrSkJSMFIyZWtGQ1EzZFJVRVJCTVc0S1lWaFNiMlJYU1hSaFJ6bDZaRWRXYTAxRVZVZERhWE5IUVZGUlFtYzNPSGRCVVhkRlNuZDNiR0ZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1Nbmg0V1hwWmVVNURPV3BaV0VsMFkyNVdkV1JIYkhSYVZFRTBRbWR2Y2tKblJVVkJXVTh2VFVGRlRrSkRiMDFMUjFKclQxUlNiRTFIUm10Tk1rMTVDbGxYVG0xTlZHUnFXVlJqTTA1dFRYbFBWRXByVGtSV2FGbFVZelJQVkZWNFdsZGFhbHBYVlhkSWQxbExTM2RaUWtKQlIwUjJla0ZDUkdkUlVrUkJPWGtLV2xkYWVrd3lhR3haVjFKNlRESXhhR0ZYTkhkSFoxbExTM2RaUWtKQlIwUjJla0ZDUkhkUlRVUkJiM2hOZWxsNFRWUnJlVTU2UlRCTlEydEhRMmx6UndwQlVWRkNaemM0ZDBGU1FVVkhkM2RhWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBUREo0ZUZsNldYbE9SRUZaUW1kdmNrSm5SVVZCV1U4dkNrMUJSVkpDUVc5TlEwUlpOVTFxVFRCT1ZGa3dUVWROUjBOcGMwZEJVVkZDWnpjNGQwRlNTVVZXVVhoVVlVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVUtXVEk1ZEV3eWVIaFplbGw1VGtNNWFsbFlTWFJqYmxaMVpFZHNkRnBUT0hWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU0yTjVPWGxhVjNoc1dWaE9iQXBNYm14MFlrVkNlVnBYV25wTU1taHNXVmRTZWt3eU1XaGhWelIzVDBGWlMwdDNXVUpDUVVkRWRucEJRa1YzVVhGRVEyaHJXa1JyTUZwVVFtaGFSRTVxQ2sxdFJtcGFha1V6V1RKRk0wNTZXbXBOYW10NVdrUlJNVmxYUlROUFJHc3hUVmRXYlZreVZteE5RMFZIUTJselIwRlJVVUpuTnpoM1FWSlJSVVYzZDFJS1pESTVlV0V5V25OaU0yUm1Xa2RzZW1OSFJqQlpNbWQzVjFGWlMwdDNXVUpDUVVkRWRucEJRa1pSVWt4RVJXeHZaRWhTZDJONmIzWk1NbVJ3WkVkb01RcFphVFZxWWpJd2RtSklSbXBPYWtrd1RESk9hR05wTVhsa1Z6VXdZVmN4YkV3eVJtcGtSMngyWW01TmRtTnVWblZqZVRoNlRtcFZlazU2U1hsT2FsVjVDazVET1doa1NGSnNZbGhDTUdONU9IaE5RbGxIUTJselIwRlJVVUpuTnpoM1FWSlpSVU5CZDBkalNGWnBZa2RzYWsxRk9FZERhWE5IUVZGUlFtYzNPSGNLUVZKblJWRlJkeTlqYlZaM1lucHdjMk5YVFRKTmFsSkJUbXByZVUxNlVURk9hbEYyV1RKR2VVeFlTakZpYmxKd1lsZFdRVTFVVFRKTlZFVTFUV3BqZUFwT1JIQjVXbGRaTm1OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVbEhTa0puYjNKQ1owVkZRV1JhTlVGblVVTkNTSE5GWlZGQ00wRklWVUV6VkRCM0NtRnpZa2hGVkVwcVIxSTBZMjFYWXpOQmNVcExXSEpxWlZCTE15OW9OSEI1WjBNNGNEZHZORUZCUVVkbk4wSlNZVlpSUVVGQ1FVMUJVbXBDUlVGcFFVZ0tSbEp4VDIwelVsazRUV0o2UlZoUFMwOHZRalJOU1c5UFIxWnBTazVNY1c5VWFFMHJVRVJSZDNoblNXZEljekpMS3paS2VsSnlUMWg2T0d3eWEzSkpWZ3BYZHpCS1JrdFRjRXhYTkVKVWJEaHZjSGhyYlRKU1NYZERaMWxKUzI5YVNYcHFNRVZCZDAxRVduZEJkMXBCU1hkSldYb3JLM0ZMTWk5c1pISjRUbXh1Q25wdlUyRmxjVGw2VVdvck9FMW9kbkZPV1hWWGJWVjRNVkFyTjFWRmMycE5jVGgyY2psWlprZzRZMDlXTm5SM1pFRnFRVUl5V0V4bFpGbEZObFJYZG1ZS2JFSTRXazF4TlZWb2RFUXdSMDFuWlhKTFJFaGliR1I2ZDB4RmNIZzJZbk0zUVVKVGJFZFhiMVJ5YWpkQmJWSmliek5WUFFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19","integratedTime":1790667152,"logIndex":2995741508,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}}
|